Basic Animation

Well it is time to start adding life into our canvas. It's alive ALIVE!!!!!!!! MWHAHAHAHAHAHAHA .......ahm. Anyway, by the end of this tutorial you should understand the basic techniques for animating in canvas. We will be talking about animation loops and how to make them and basic vectoring of objects.

Animation Loops

There are several ways in which to create an animation loop. Some are definetly better than others. Behind every animation loop is the idea of drawing, waiting, drawing, and waiting. We can do this by using setInterval, setTimeout, and probably the best way requestAnimationFrame. So let's go through these three.

setInterval

If your not new to JavaScript I'm sure you will have used this and setTimeout at some point. Now this is not the way I would suggest making your animation. setInterval must be called outside the loop. It is one call and if you accidentally say call it a second time for your animation loop it will bog down the system. Seriously don't use this. Depending on the argument that you give setInterval it will continue to run whatever instructions you pass into it over and over and over again until clearInterval is called. In order for clearInterval to work however the setInterval needs an id. The id is a variable that you store it in like a pointer. It works like this.

  var animation = setInterval(alert("hello"), 1000/ 60 );
clearInterval(animation);

What this will do is set the site to alert "hello" every 16 milliseconds or 60 calls per second. Now with clearInterval being called right after it will never make it to the first alert but that is how you would stop the 'animation'.

As an example of how the animation is built please take a look at this.

Now you don't have to call this with 1000/60 as the time. You can put anything in there. Just remember that it is set to milliseconds. So 1000 would be once every second, 20 would be every 20 ms, etc.

setTimout

Similar to setInterval except instead of setting a regular action this is more of a delay this action until. Just like with setInterval though you will put an action in and a timer in milliseconds. It goes a little something like this

  // Without an id
setTimeout(alert('hello'), 1000);

// With Id

var id = setTimeout(alert('hello'), 1000);
clearTimeout('id');

Now you can do setTimout without an id but if you do you will not be able to stop the action being performed. clearTimeout requires an id, so if you are using this for an animation in say a game and want to give the user an option to pause the game you will need an id.

An example using setTimeout is thusly

Now setTimeout because it calls a delay does need to be called every iteration of the loop. If you do not the animation loop will be called once then never called again.

requestAnimationFrame

This is the best way to work with animations in cavasn. the requestAnimationFrame API controls the frame rate in the browser. It is a smoother animation, you do not loose frames and it is really easy to use. Now this is one of those situations where each brower has it's own version of this. So we will create a really simple polyfill to take care of that problem.

First we will add a window method. This is our variable we will be working with in the animation. You can call it what you like personally I use reqFrame

  window.reqFrame = 

Next we will create an IIFE

  window.reqFrame = (function(){

})();

This way once the reqFrame variable is assigned it will be the return of our function that we are building. Now what we are going to do is use an interesting quirk of the || (or) combined with the fact that non declared functions are returned undefined which in javascript is a falsey value. We can use this to define default values. What happens is JavaScript will evaluate the first expression in the or block. If it is true it will use that and stop evaluating the or block because frankly if one or is true the whole |or| block is true. If it is false it will move on to the next expression in the |or| block. if that is true it will use that expression and so on and so forth. So if we have our expression return a specific code that the browser uses it will check to find out if the method exsists first. If it does the expression evaluates to true and the statement is returned if not it will move to the next |or| expression. Now just like in CSS the requestAnimationFrame browser prefixes are exactly the same. So we can create our return statment like so

  window.reqFrame = (function(){
    return window.webkitRequestAnimationFrame || // Chrome/Safari request animation frame
        window.mozRequestAnimationFrame      ||  // Mozilla Firefox request animation frame
        window.msRequestAnimationFrame        || // IE request animation frame
      window.oRequestAnimationFrame           || // Opera request animation frame
      window.requestAnimationFrame              ; // Normal request animation frame
})();

Ok now we have all of the prefixes taken care of. If we are for instance using Opera the function will return oRequestAnimationFrame because every possible return value before it doesn't exsist as a method in the window object. What if the browser doesn't support requestAnimationFrame at all. We will add in one last |or| expression

  window.reqFrame = (function(){
    return window.webkitRequestAnimationFrame || // Chrome/Safari request animation frame
        window.mozRequestAnimationFrame      ||  // Mozilla Firefox request animation frame
        window.msRequestAnimationFrame        || // IE request animation frame
      window.oRequestAnimationFrame           || // Opera request animation frame
      window.requestAnimationFrame              || // Normal request animation frame
      function(callback){
        window.setTimout(callback, 1000/60);
      };
})();

Finally we are done. If the browser doesn't support requestAnimationFrame at all it will use a setTimeout with your callback as the action and 60 FPS as the delay. Now there are other ways you can do this like itterating over and array of prefixes and concating them to requestAnimationFrame. Personally not an issue for me to just add this block of code to the begining of my animation. Like I said though this is a simple polyfill.

Example:

Finally as the opposite of requestAnimationFrame there is a cancelAnimationFrame. This works exactly the same way, prefix + cancelAnimationFrame. Now to add in the setTimout being cleared you will need to change the requestAnimationFrame poly fill a little.

  (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);
    };
}()); 


So let's use this and create our first animation. something simple like a spinning triangle.

To start off let's create our canvas and set it's basic properties just like all our other canvas builds.

  var canvas = document.body.appendChild(document.createElement('canvas'));
var ctx = canvas.getContext('2d');

var W = window.innerWidth;
var H = window.innerHeight;


canvas.width = W;
canvas.height = H;

Then we will add in the animationFrame polyfill

  var canvas = document.body.appendChild(document.createElement('canvas'));
var ctx = canvas.getContext('2d');

var W = window.innerWidth;
var H = window.innerHeight;


canvas.width = W;
canvas.height = H;

window.reqFrame = (function(){
    return window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        window.oRequestAnimationFrame ||
        window.requestAnimationFrame ||
        function(callback){
        window.setTimeout(callback,1000/60);
    };
})();

Now we need something to draw. No point in making an animation if we aren't drawing anything. So we will be creating an object to draw. Unlike before we are going to encapsulate our drawing in an object litteral. This way we can more easily update our drawing and define the logic of the animation in a more smooth and clean pattern.

  var triangle = {
// First we set the demensions of our object 
// The format I am using is pointX and pointY
    px1: 0,
    py1: -50,
    px2: -50,
    py2: 50,
    px3: 50,
    py3: 50,
// This next demension is the angle of rotation on the object
    angle: 0,

// We then define a function to draw our object     
    draw: function(){
  // Save the current canvas state and translate it so that origin is in the middle
  // Then we take our angle and rotate the canvas around that point
        ctx.save();
        ctx.translate(W/2,H/2);
        ctx.rotate(this.angle * Math.PI / 180); // Remeber angle in degrees times PI/180 is angle in radians which is the input for rotate

  // Next we draw our actual triangle using the points we defined this shape has
        ctx.strokeStyle = "white"; // You can put what ever color your heart desires
        ctx.fillStyle = "blue";
        ctx.lineWidth = 3;
        ctx.beginPath();
        ctx.moveTo(this.px1,this.py1);
        ctx.lineTo(this.px2,this.py2);
        ctx.lineTo(this.px3, this.py3);
        ctx.closePath(); // We close the path so that the last line from px3/py3 is draw back to our original starting points
        ctx.stroke();
        ctx.fill();
        ctx.restore(); // We lastly restore our canvas so that anything else drawn is not affected by rotate and translate
    },

// Next we create a function to handle the logic of our object
// This one is pretty easy since all we are doing is rotating. Just add a to
// the current angle of the object
    update: function(){
        this.angle += 5; // Add 5 degrees to the angle we are at currently
    }
}

Now we have the basics and our object taken care of let's create our animation loop. They all work the same way.

  1. Clear away the old frame.
  2. Update the logic of the animation.
  3. Draw the new frame.
  4. Call the next frame.

From games to flash animation this is the basics. This is a pattern you will get used to really really fast. Now in addition, I like to create a function to clear my canvas. This helps to keep our main animation loop simple and clean.

  function cleanUp(){
    ctx.clearRect(W / 2 - 100, H/2 - 100, 200, 200); \\This way we only clear the spot the drawing is on. Helps with efficiency
    ctx.fillStyle = "black";
    ctx.fillRect(W / 2 - 105, H/2 - 105, 210, 210);
}


function loop(){
    cleanUp();  //Clean our frame of the old frame
    triangle.update(); // Update our object
    triangle.draw(); // Draw our new object
    reqFrame(loop); // Call the next frame
}

Lastly we will call our loop() function to get the loop started and we are done.

  var canvas = document.body.appendChild(document.createElement('canvas'));
var ctx = canvas.getContext('2d');

window.reqFrame = (function(){
    return window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        window.oRequestAnimationFrame ||
        window.requestAnimationFrame ||
        function(callback){
        window.setTimeout(callback,1000/60);
    };
})();

var W = window.innerWidth;
var H = window.innerHeight;
canvas.width = W;
canvas.height = H;

var triangle = {
    px1: 0,
    py1: -50,
    px2: -50,
    py2: 50,
    px3: 50,
    py3: 50,
    angle: 0,

    draw: function(){
        ctx.save();
        ctx.translate(W/2,H/2);
        ctx.rotate(this.angle * Math.PI / 180);
        ctx.strokeStyle = "white";
        ctx.fillStyle = "blue";
        ctx.lineWidth = 3;
        ctx.beginPath();
        ctx.moveTo(this.px1,this.py1);
        ctx.lineTo(this.px2,this.py2);
        ctx.lineTo(this.px3, this.py3);
        ctx.closePath();
        ctx.stroke();
        ctx.fill();
        ctx.restore();
    },

    update: function(){
        this.angle += 5;
    }
}

function cleanUp(){
    ctx.clearRect(W / 2 - 100, H/2 - 100, 200, 200);
    ctx.fillStyle = "black";
    ctx.fillRect(W / 2 - 105, H/2 - 105, 210, 210);
}


function loop(){
    cleanUp();
    triangle.update();
    triangle.draw();
    reqFrame(loop);
}

loop();


And the result of this magic

WOOOOOOOOOOOOO...................yeah not to thrilling I know. However, this will build and more advanced things will be created from this foundation. Next time we will be looking into adding environmental factors like gravity to our animation. That's right let's start with physics modeling. YaY!


1,353 0 0