tadhg.com
tadhg.com
 

Animating Circles with JavaScript

23:53 Mon 19 Feb 2007
[, , , ]

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.

Circle Diagram

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:

Circle Diagram Two

It should be evident that y1= yd. 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:

Circle Diagram Three

Since x1 = xe:

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

3 Responses to “Animating Circles with JavaScript”

  1. Kaustav Mukherjee Says:

    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..

    $(function() {
    $("div#hot").bind('click',function() {
    $("div#hot").unbind('click');
    var phi=0;
    var int= 2*(Math.PI)/1000;
    function cool() {
    phi= (phi>=2*(Math.PI))? 0:(phi+int);
    var $m= 400-200*Math.cos(phi);
    var $n= 250-200*Math.sin(phi);
    $("div#hot").animate({marginLeft: $m+'px', marginTop: $n+'px'},1,cool);
    }
    cool();
    });
    });
    

  2. Tadhg Says:

    Kaustav: My code was indeed convoluted, and its structure very old—thanks for providing a better version!

  3. dongnd Says:

    @ Tadhg : thanks for sharing your knowgled. This tut help me understand the essence of the problem.

Leave a Reply