clock

Canvas Animation Basics

For me, one of the most fascinating improvements to the web as been the advancements with in-browser animations and gaming.

The HTML5 canvas element has opened up a lot of opportunities and now we can program animations and games that run directly and natively in the browser without the need of Java applets or third party plug-in dependent tech like Flash.

To get things started, we need to include the canvas tag in our HTML like so:

<canvas id="canvas" width="400" height="200" />

Setup

That will render our blank canvas DOM element on the page. Now, to get things running we need to target it with our JavaScript, set our context (which will always be 2D for our purposes) and then for this example we will be setting our FPS:

// Variables
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var fps = 15;

Now, we will create an init() variable to call at the end of the script to get things running. Of course, in this script we could just simply call the draw function, but in many cases you will need to do more besides calling a single function on init. It's good practice to do things this way.

// Initialize All The Things!
function init() {
    draw();
}

Draw, Draw, Draw!

Now, this is where the "magic happens" so to speak. We will create our draw function that will recursively continue calling itself and therefore generating our frames of animation. Luckily, we now have requestAnimationFrame available which handles our animations much more efficiently and wont continue to run in the background when our tab isn't in focus.

But, there was one downfall to requestAnimationFrame. You couldn't set a framerate! However, there is very simple way around that! As you can see below we simply wrap our draw() code within a setTimeout which will allow us to still control our framerate while keeping the benefits of requestAnimationFrame intact! Neat, eh?

// Draw on our canvas.
function draw() {

    // To Set A Framerate Using requestAnimationFrame
    setTimeout(function() {

        // This Animates A Boring Squiggly Line
        ctx.clearRect(0,0,400,300);
        requestAnimationFrame(draw);
        ctx.beginPath();
        ctx.moveTo(Math.random() * 70, Math.random() * 70);
        ctx.lineTo(Math.random() * 70, Math.random() * 70);
        ctx.lineTo(Math.random() * 70, Math.random() * 70);
        ctx.lineTo(Math.random() * 70, Math.random() * 70);
        ctx.lineTo(Math.random() * 70, Math.random() * 70);
        ctx.lineTo(Math.random() * 70, Math.random() * 70);
        ctx.lineTo(Math.random() * 70, Math.random() * 70);
        ctx.lineTo(Math.random() * 70, Math.random() * 70);
        ctx.lineTo(Math.random() * 70, Math.random() * 70);
        ctx.lineTo(Math.random() * 70, Math.random() * 70);
        ctx.lineTo(Math.random() * 70, Math.random() * 70);
        ctx.strokeStyle = "#333";
        ctx.stroke();
    }, 1000 / fps);

}

Additional Notes

"But, my animation doesnt work in browser X!" Don't fret, there is hope yet! Web rockstar Paul Irish has provided us with a wonderful requestAnimationFrame shim.

(function() {
    var lastTime = 0;
    var vendors = ['ms', 'moz', 'webkit', 'o'];
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
        window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] 
                                   || window[vendors[x]+'CancelRequestAnimationFrame'];
    }

    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function(callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 
              timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };

    if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
}());