JavaScript Day 7: Animation

Mar 07 2011 Published by under JavaScript

Send to Kindle

Today, let’s talk about animating in JavaScript. Making things move!

In Flash, there is the eternal battle of which is better, enterFrame or timer? In JavaScript, you have a timer. You don’t actually have a Timer class like you do in ActionScript, but you do have a setInterval function that is, from what I can tell, exactly what it has always been in ActionScript.

Here’s a quick example. This assumes you have a canvas in your html, named canvas, and this script is included in the same page, yadda yadda.

$(function() {
    var x = 0, y = 0, vx = 3, vy = 2, canvas, context;
    
    canvas = $("#canvas")[0];
    context = canvas.getContext("2d");
    
    setInterval(draw, 1000/24);
    
    function draw() {
        context.clearRect(0, 0, 500, 500);
        context.beginPath();
        context.arc(x, y, 20, 0, Math.PI * 2, false);
        context.fill();
        x += vx;
        y += vy;
    }
});

Pretty straightforward, eh? You set an interval to run at 24 fps, and set it to call the draw function. That function clears the canvas, draws a circle with a fill, updates the x and y. There are some alternate ways to do this. One is to use setTimeout instead of setInterval. The former only executes a single time, so you’d need to call it after every execution of the draw function to make sure it gets called again.

$(function() {
    var x = 0, y = 0, vx = 3, vy = 2, canvas, context;
    
    canvas = $("#canvas")[0];
    context = canvas.getContext("2d");
    draw();
    
    function draw() {
        context.clearRect(0, 0, 500, 500);
        context.beginPath();
        context.arc(x, y, 20, 0, Math.PI * 2, false);
        context.fill();
        x += vx;
        y += vy;
        setTimeout(draw, 1000/24);
    }
}

One, um… advantage of using setTimeout is that you can pass a string instead of a function reference. The string will be evaluated as JS code and executed. To do this in the above example requires a bit of manipulation. This is because the evaluated string is scoped to the global scope and will not be able to see the stuff in the local scope that we’ve taken so much care to create. So we have to do something like this:

var draw;
$(function() {
    var x = 0, y = 0, vx = 3, vy = 2, canvas, context;
    
    canvas = $("#canvas")[0];
    context = canvas.getContext("2d");
    
    draw = function() {
        context.clearRect(0, 0, 500, 500);
        context.beginPath();
        context.arc(x, y, 20, 0, Math.PI * 2, false);
        context.fill();
        x += vx;
        y += vy;
        setTimeout("draw()", 1000/24);
    }

    draw();

});

Here we define draw on the global scope so it will be accessible by the evaluated string. The function is then defined as an anonymous function and assigned to the global draw var. The setTimeout call now passes in the string, “draw()” which will be correctly evaluated to the draw function now. Finally, we move the initial draw call to the bottom so that the draw function will be defined when we call it.

I’m not recommending this method at all, mainly putting it here because you might see it from time to time. I can see that it might be useful if the function call might change, and needed to be dynamically evaluated at run time. But chances are there are still better ways to do this. In this case, though, I don’t see any advantage at all, only a mess.

I should also mention that setInterval and setTimout both return a value that can be used to stop the timer. Just like ActionScript. It’s an int value. Just create a variable to hold it, assign the result of the setInterval or setTimeout call to that variable. Later when you want to stop the animation (or whatever you’re controlling), call clearInterval(n), passing in the value you saved.

var interval;

// ... later ...

interval = setInterval(foo, 1000/24);

// ... later ...

clearInterval(interval);

Also, in keeping with JavaScript mentality, we see that in the first example using setInterval (the one I recommend), that the draw function is only ever called by the setInterval call. There’s no real reason for it to be a named function. We can inline it as an anonymous function right into the setInterval call itself:

$(function() {
    var x = 0, y = 0, vx = 3, vy = 2, canvas, context;
    
    canvas = $("#canvas")[0];
    context = canvas.getContext("2d");
    
    setInterval(function () {
        context.clearRect(0, 0, 500, 500);
        context.beginPath();
        context.arc(x, y, 20, 0, Math.PI * 2, false);
        context.fill();
        x += vx;
        y += vy;
    }, 1000/24);
    
});

I admit that a part of me cringes when doing this. But I’m trying to see the world through the eyes of a JavaScript coder, where multiple, nested anonymous functions are the norm, and even become readable. I know a few are going to stop by and comment, “see??? this is why javascript sucks! you’re taking us back to AS1 days!” To you, I say, “chill”.

Finally, since it’s been mentioned here and there, there is another emerging way to do animations in JavaScript called “requestAnimationFrame”. It’s not completely finalized, not supported by all browsers, and not consistently implemented, so make sure you read up on it thoroughly before using it in any final projects. Rather than regurgitate what I know of it, I will direct you to this excellent article by Paul Irish, which should tell you all you need to know.

And, finally, finally, here’s a link to today’s example:

http://www.bit-101.com/jscanvas/mar07.html

Send to Kindle

5 responses so far. Comments will be closed after post is one year old.