ZoEM
ZoEM

Reputation: 852

Dynamically add new elements in function

I’ve programmed a function that rotates objects around a main object, like how planets rotate around the sun.

I'm trying to dynamically add new planets in my little solar system by a simple click of a button. They are all SVG elements. I'm having trouble working out how I can dynamically generate new elements that rotate around the sun with my rotation(coorX, coorY, object) function. They would all need to be dynamically named and dynamically positioned, this is too hard on me.

What should my code need to look like in order for me to achieve this? Thank you in advance for any help/tips.

Here's my code:

var objectX = "black";
function addObject(){
  objectX = "blue";
}

function rotation(coorX, coorY, object) {
	object.side +=  (1.0 / object.speed); 
	var ang = object.side * 2.0 * Math.PI / 180.0; 
	var r = object.spin;
 
	return {
		x: Math.cos(ang) * r - Math.sin(ang) * r + coorX, 
		y: Math.sin(ang) * r + Math.cos(ang) * r + coorY 
	};
  }  
  
  function rotationball ( circle ) {
	var x, y, x_black, y_black, e, newpos;
	e = document.getElementById ( circle );
	x_black = parseFloat ( document.getElementById ( objectX ).getAttribute ( "cx" ) );
	y_black = parseFloat ( document.getElementById ( objectX ).getAttribute ( "cy" ) );
	newpos = rotation( x_black, y_black, ball[circle] );
  
	e.setAttribute ( "cx", newpos.x );
	e.setAttribute ( "cy", newpos.y );
	}   
    
  var ball = { 
  blue:	{speed: 1.2, 	spin: 100,	side: 0.0} , 
  red:	{speed: 1.2, 	spin: 200,	side: 0.0}
	};
  
  function animate () {
  	rotationball("blue"); 
  	rotationball("red");   
	}       

var animateInterval = setInterval(animate, 1000 / 60);
.st0{fill:black;}
.st1{fill:blue;}
.st2{fill:red;}
<div class="spinning"> <svg xmlns="http://www.w3.org/2000/svg" id="solly" viewBox="0 0 1000 600"><g id="Sun2">
		<circle id="black" class="st0" cx="500" cy="300.8" r="10"/>
		<circle id="blue" class="st1" cx="375.4" cy="289.7" r="10"/>	
    <circle id="red" class="st2" cx="375.4" cy="289.7" r="10"/>	
  
</div>
<button type="button" onclick="addObject()">
button
</button> 
PS: I was also going to program that each added planet would be positioned a bit farther from the central point (the sun) than the previously added planet.

Upvotes: 4

Views: 93

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074008

You're very much on the right track with your ball object (though I'd call it balls). To dynamically create a circle element you do this:

var circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");

Then you set its attributes (circle.setAttribute) and append it to the id="Sun2" element.

Here's a makeBall function that does that on the basis of a specification object you pass in which has two subobjects: element, which defines the element, and animation which defines the animation:

function makeBall(spec) {
  // Create the element
  var circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
  // Set its various attributes
  ["id", "cx", "cy", "r", "class"].forEach(function(attrName) {
    circle.setAttribute(attrName, spec.element[attrName]);
  });
  // Add it to the sun
  document.getElementById("Sun2").appendChild(circle);
  // Remember its animation settings in `ball`
  ball[spec.element.id] = spec.animation;
}

Then you use it like this:

makeBall({
  element: {id: "blue", class: "st1", cx: "375.4", cy: "289.7", r: "10"},
  animation: {speed: 1.2, spin: 100, side: 0.0}
});

The last piece is to replace animate with something that works dynamically with the properties inside ball:

function animate() {
  Object.keys(ball).forEach(function(id) {
    rotationball(id);
  });
}

Here's an example removing blue and red from the markup and adding them dynamically when we start. I've also gotten rid of objectX and make the rotation always be relative to black, and hooked up some fields to use when adding a new ball:

var ball = {};

makeBall({
  element: {id: "blue", class: "st1", r: "10"},
  animation: {speed: 1.2, spin: 20, side: 0.0}
});
makeBall({
  element: {id: "red", class: "st2", r: "10"},
  animation: {speed: 1.2, spin: 40, side: 120.0}
});
makeBall({
  element: {id: "yellow", class: "st3", r: "10"},
  animation: {speed: 1.2, spin: 60, side: 240.0}
});

function makeBall(spec) {
  // Create the element
  var circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
  // Set its various attributes
  ["id", "cx", "cy", "r", "class"].forEach(function(attrName) {
    if (spec.element[attrName]) {
      circle.setAttribute(attrName, spec.element[attrName]);
    }
  });
  // Add it to the sun
  document.getElementById("Sun2").appendChild(circle);
  // Remember its animation settings in `ball`
  ball[spec.element.id] = spec.animation;
}

function addObject() {
  // Create a spec to use with makeBall from the fields
  var spec = {
    element: {
      id: document.getElementById("new-id").value,
      class: document.getElementById("new-class").value,
      r: parseFloat(document.getElementById("new-r").value)
    },
    animation: {
      speed: parseFloat(document.getElementById("new-speed").value),
      spin: parseFloat(document.getElementById("new-spin").value),
      side: parseFloat(document.getElementById("new-side").value)
    }
  };
  // Some minimal validation
  if (!spec.element.id || !spec.element.r || !spec.animation.speed || !spec.animation.spin || isNaN(spec.animation.side)) {
    alert("Need all values to add a ball");
  } else if (ball[spec.element.id]) {
    alert("There is already a ball '" + spec.element.id + "'");
  } else {
    // Do it!
    makeBall(spec);
  }
}

function rotation(coorX, coorY, object) {
  object.side += (1.0 / object.speed);
  var ang = object.side * 2.0 * Math.PI / 180.0;
  var r = object.spin;

  return {
    x: Math.cos(ang) * r - Math.sin(ang) * r + coorX,
    y: Math.sin(ang) * r + Math.cos(ang) * r + coorY
  };
}

function rotationball(circle) {
  var x, y, x_black, y_black, e, newpos, black;

  // We always rotate around black
  black = document.getElementById("black");
  
  // Get this circle and update its position
  e = document.getElementById(circle);
  x_black = parseFloat(black.getAttribute("cx"));
  y_black = parseFloat(black.getAttribute("cy"));
  newpos = rotation(x_black, y_black, ball[circle]);

  e.setAttribute("cx", newpos.x);
  e.setAttribute("cy", newpos.y);
}

function animate() {
  Object.keys(ball).forEach(function(id) {
    rotationball(id);
  });
}

var animateInterval = setInterval(animate, 1000 / 60);
.st0 {
  fill: black;
}
.st1 {
  fill: blue;
}
.st2 {
  fill: red;
}
.st3 {
  fill: yellow;
}
.st4 {
  fill: orange;
}
<div>Add ball:
  <label>
    ID: <input type="text" id="new-id" value="newball">
  </label>
  <label>
    R: <input type="text" id="new-r" value="10">
  </label>
  <label>
    Speed: <input type="text" id="new-speed" value="1.2">
  </label>
  <label>
    Spin: <input type="text" id="new-spin" value="80">
  </label>
  <label>
    Side: <input type="text" id="new-side" value="0.0">
  </label>
  <label>
    Class: <input type="text" id="new-class" value="st4">
  </label>
  <button type="button" onclick="addObject()">
    Make Ball
  </button>
</div>

<div class="spinning">
  <svg xmlns="http://www.w3.org/2000/svg" id="solly" viewBox="0 0 1000 600">
    <g id="Sun2">
      <circle id="black" class="st0" cx="500" cy="300.8" r="10" />
    </g>
  </svg>
</div>


Side note: You might look at using requestAnimationFrame rather than setInterval.

Side note 2: As far as I can tell, what you've called spin is actually the distance from the center (e.g., the radius of the circle each ball will describe around the black ball).

Upvotes: 5

Related Questions