Animating Circles with JavaScript
Years back I wrote some online tutorials on how to animate circular movement in Lingo and JavaScript. While moving my older articles to this site, I realized that I could not in good conscience move this one without updating it. So, a tutorial on circular animation in JavaScript follows.
First, some basic math.
We want to move an element in a circle. The element is a point that moves around the edge of this circle. At any given time we will have the radius of the circle, its center, and the angle at which the point is. Call the radius r, the center x,y and the angle a.
So, from the above diagram, what we want to be able to work out is x1,y1 for any given values of a, r and x,y. To work out x1,y1 we need to reinterpret the arc of the circle as a right-angle triangle:
It should be evident that y1= y – d. In a normal co-oridinate system it would be y + d, but almost all computer graphical systems have 0,0 at the top left corner, not the bottom left.
The sin of an angle = the opposite over the hypoteneuse, and cos = the adjacent over the hypoteneuse. Therefore:
sin a = d / r r sin a = d d = r sin a
and so:
y1 = y - (r sin a)
As for x1:
Since x1 = x – e:
cos a = e / r r cos a = e e = r sin a
and so:
x1 = x - (r cos a)
So now we can express x1,y1 in terms of the values that we set.
(In the original, I was dealing with Lingo, the Director scripting language. I will include this script at the end, but with no claims for its accuracy, as about eight years have passed since I wrote it and I have no idea whatsoever about how Lingo works these days.)
One of the gotchas when I first tried this was that JavaScript thinks in radians, not degrees. Radians = (Degrees * (pi/180)), or:
function convertDegreesToRadians(degrees) {
var pi = Math.PI;
var radians = (degrees * (pi/180));
return radians;
}
We need an element to work on, but we can create one at run time using DOM methods, so we’ll leave that for the moment.
We need animation, or a looping function. For this we use setInterval()
. setInterval()
takes two arguments: the function to run, and the interval after which it should be run (repeatedly). Before getting to that, here is the skeleton of the functions we’ll create and what we’re using them for:
var circleMove = {
Degrees: 0,
Radius: 100,
Rads: 0,
timeId: 0,
moveElement: function(element, newLocX, newLocY) {
//Does the actual shifting of the element; takes element and x and y coordinates.
},
calculateCoordinates: function(theCenterPointX, theCenterPointY) {
//sets the degree variable, and loops if it's greater than 360, then calculates the Rads by calling convertDegreesToRadians, and then x, and y
},
convertDegreesToRadians: function(degrees) {
//calculates radians from degrees
},
circleElement: function(element, theCenterPointX, theCenterPointY, delayTime) {
//gets the new coordinates from calculateCoordinates() and sends them to moveElement()
},
init: function(element, theCenterPointX, theCenterPointY, delayTime) {
//clears the setInterval, and then sets it again. Also, creates the element if it does not exist.
}
}
We’re first going to initialize the whole thing.
init: function(elementId, theCenterPointX, theCenterPointY, delayTime) {
//first, check to see if the element exists:
var theElement = null;
if (!document.getElementById(elementId)) {
//if not, create it and give it some properties
theElement = document.createElement("div");
theElement.id = elementId;
theElement.style.height = "1em";
theElement.style.width = "1em";
theElement.style.border = "1px solid red";
document.body.appendChild(theElement);
} else {
theElement = document.getElementById(elementId);
}
//removes the looping on the element if it's already been set:
clearInterval(circleMove.timeID);
//starts the repetition.
circleMove.timeID = setInterval( function() {
circleMove.circleElement(theElement, theCenterPointX, theCenterPointY, delayTime);
}, delayTime);
}
Next up is circleElement, which is little more than a wrapper function:
circleElement: function(element, theCenterPointX, theCenterPointY, delayTime) {
var newCoordinates = circleMove.calculateCoordinates(theCenterPointX, theCenterPointY);
circleMove.moveElement(element, newCoordinates[0], newCoordinates[1]);
},
calculateCoordinates: function(theCenterPointX, theCenterPointY) {
//if the degree variable is greater than 360, reset it
if (circleMove.Degrees > 360) {
circleMove.Degrees = 1;
}
//increment the degree variable, then get the radians, and then calculate the coordinates
circleMove.Degrees = circleMove.Degrees + 1;
circleMove.Rads = circleMove.convertDegreesToRadians(circleMove.Degrees);
var newLocX = (theCenterPointX - (circleMove.Radius * Math.cos(circleMove.Rads)) );
var newLocY = (theCenterPointY - (circleMove.Radius * Math.sin(circleMove.Rads)) );
//return the coordinates as a two-value array
return [newLocX, newLocY];
},
moveElement: function(element, newLocX, newLocY) {
//sets the top and left of the element style, thus moving it to its new location.
element.style.position = "absolute";
element.style.top = newLocX + "px";
element.style.left = newLocY + "px";
},
Finally, how do we call "init()" properly? We add a function that adds the init call itself to an element (and uses John Resig's addEvent), and then have that fire when the window loads:
initStart: function() {
function callInit() {
circleMove.init("theMovingElement", this., 300, 10);
}
circleMove.addEvent(document.getElementById("theButton"), "click", callInit);
},
addEvent: function( obj, type, fn ) {
//John Resig's function for adding event handlers to objects.
if ( obj.attachEvent ) {
obj['e'+type+fn] = fn;
obj[type+fn] = function(){obj['e'+type+fn]( window.event );}
obj.attachEvent( 'on'+type, obj[type+fn] );
} else {
obj.addEventListener( type, fn, false );
}
}
circleMove.addEvent(window, "load", circleMove.initStart);
Lastly, we need .
You can view it all together (the version used also uses a script to calculate the current scroll position, so you can see the function work when you press that button!).
Finally, in case anyone's interested, here is that Lingo script (use at your own risk!):
global gTheCenterPointy, gTheCenterPointx, gDegrees, gRadius, gRads on exitFrame set gDegrees = gDegrees + 1 if gDegrees > 360 then set gDegrees = 1 set radValue = float (57.2958) set gRads = (float(gDegrees)/radValue) set newLocV= (gTheCenterPointy - (gRadius * ( sin (float(gRads))))) set newLocH = (gTheCenterPointx - (gRadius * (cos (float(gRads))))) set the locV of sprite 2 to newLocV set the locH of sprite 2 to newLocH go to the frame end
09 Feb 2011 at 12:39
This program is so very convoluted! I wrote this rather small script to effect the same animation:
(Assuming the beginning co-ordinates of the box to be 200px to the right and 250px from the top and radius of the circle to be 200px, i.e. centre is at 400px to the right and 250px from the top. If you want different figures, change the values accordingly, it is very easy.). The HTML and CSS is not given..
09 Feb 2011 at 13:27
Kaustav: My code was indeed convoluted, and its structure very old—thanks for providing a better version!
07 Oct 2011 at 07:54
@ Tadhg : thanks for sharing your knowgled. This tut help me understand the essence of the problem.