Codular

HomeWriters RSS

Animation with HTML5 Canvas

Introduction

In this article, we're going to look at some of the techniques used to produce animation on a canvas, like a player moving position in a game. We'll also take a look at adding keyboard control to the box.

Before I start, I would like to advise you to read the earlier articles Beginner's Guide to HTML5 Canvas and More Beginner HTML5 Canvas as we're going to be making use of a lot of the techniques mentioned.

How does animation work?

Animation works by changing the properties of the element, or layer, in such small increments, that it looks smooth to the user. If you slow down animation, and make these increments larger, you'll actually be able to see the element jumping between the different increments.

The Code

So with that, let's get into it. The first step is, obviously, to create the canvas.

<canvas width='200' height='200' id='canvas'>Your browser does not support canvas - go get Chrome!</canvas>

We're also going to draw a square in the middle of the canvas.

var canvas = document.getElementById('canvas');

if (canvas.getContext) {
  var context = canvas.getContext('2d');

  context.beginPath();
  context.rect(50, 50, 100, 100);
  context.fillStyle = '#000000';
  context.fill();
}

That's great, but as it is, this would be horrible for animation, so we need to put it in a function that can be reused much more easily. We'll start by first defining the function, we'll name it render. We'll also put out current render code in there too.

var render = function() {
    context.beginPath();
    context.rect(50, 50, 100, 100);
    content.fillStyle = '#000000';
    content.fill();
};

render();

We'll also want to make sure we're clearing the canvas before drawing to it, as it will make it easier to change our drawing later on. We'll do this by using the function clearRect(). This is used exactly the same way that rect() is, but instead of drawing to the canvas, it clears it. We'll be wanting to clear the full canvas, so we can use canvas.width and canvas.height for the width and height parameters.

var render = function() {
    context.clearRect(0, 0, canvas.width, canvas.height);
    context.beginPath();
    context.rect(50, 50, 100, 100);
    context.fillStyle = '#000000';
    context.fill();
};

The next step is to make an object to store various parameters of our square, and alter the render function to use this object. This will make updating the square later on much easier:

var square = {
    'x': 50,
    'y': 50,
    'width': 100,
    'height': 100,
    'fill': '#000000'
};

var render = function() {
    context.clearRect(0, 0, canvas.width, canvas.height);
    context.beginPath();
    context.rect(square.x, square.y, square.width, square.height);
    context.fillStyle = square.fill;
    context.fill();
};

In order to produce animation, we must use a technique aptly named redrawing, which is where we repeatedly draw something, in this case the element, to the screen as often as possible. In the past, this was done using standard delay functions, such as setTimeout and setInterval, but now we can use a function called requestAnimationFrame. However, because this is still not fully supported, we must accommodate for various browser implementations, and replicate it if it doesn't exist at all.

That's quite easy to do with the below block of code. This might look complex, but actually all it's doing is attempting each variation of requestAnimationFrame for each browser, before falling back to our own function. This works because if you ask an object (in this case window) for a property, that doesn't exist, it will return undefined. Ultimately we're saying if this property is undefined, try the next property, if that's undefined, try the next one, etc... until we eventually just define it ourselves:

var requestAnimationFrame =  
        window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        window.oRequestAnimationFrame ||
        function(callback) {
          return setTimeout(callback, 1);
        };

We can now make the render function redraw the square using the above function

var render = function() {
    // Clear the canvas
    context.clearRect(0, 0, canvas.width, canvas.height);

    // Draw the square
    context.beginPath();
    context.rect(square.x, square.y, square.width, square.height);
    context.fillStyle = square.fill;
    context.fill();

    // Redraw
    requestAnimationFrame(render);
};

// Start the redrawing process
render();

Because we have the render function drawing the square based on the object of properties, all we have to do now is to update the square's properties, and the render function will redraw with them.

Next, we must write the function that will actually animate the square. This function will take the property to update (out of x, y, width or height), the value to update it to, and the duration of the animation.

var animate = function(prop, val, duration) {

};

Inside of this function, we need to do a couple of calculations, that will help us determine the progress of the animation. We'll reference the property they want to animate using square[prop], which is the same as saying square['x'] or square.x when prop equals 'x'. First we calculate the time that the animation started, and then when it should end, then finally work out the distance remaining of the animation.

var start = new Date().getTime();
var end = start + duration;
var current = square[prop];
var distance = val - current;

Because we need to increment the property in steps, we need a step function. This is also where you would do the calculations required for easing, though that is outside the scope of this tutorial.

var step = function() {

};

return step();

Inside the so-called step function, we need to calculate the current progress of the function, so we can work out what to set the property to. There is a little maths behind this, but it's quite simple. Where timestamp is the current timestamp in the step.

(duration - (end - timestamp))) / duration

However, we can never allow this number to be more than 1, otherwise the square will go further than intended, so we'll use the Math.min function. This function returns the smallest number out of the two arguments. So if the equation did return a number more than one, the function would still only return 1:

var min = Math.min(1, 2); // min = 1

Using this progress variable, we can calculate the value of the property for this stage of the animation. Lastly, we need to repeat the step function if the animation hasn't ended yet.

var timestamp = new Date().getTime();
var progress = Math.min((duration - (end - timestamp)) / duration, 1);
if (progress < 1) requestAnimationFrame(step);

With that, your animation should be working. For clarity, I'm going to give you the full animate function.

var animate = function(prop, val, duration) {
  // The calculations required for the step function
  var start = new Date().getTime();
  var end = start + duration;
  var current = square[prop];
  var distance = val - current;

  var step = function() {
    // Get our current progres
    var timestamp = new Date().getTime();
    var progress = Math.min((duration - (end - timestamp)) / duration, 1);

    // Update the square's property
    square[prop] = current + (distance * progress);

    // If the animation hasn't finished, repeat the step.
    if (progress < 1) requestAnimationFrame(step);
  };

  // Start the animation
  return step();
};

animate('x', 0, 1000);

Examples

The most basic example is moving the square around, which can be done quite easily.

To move the square horizontally across the canvas, we can use the below code that will alter the x value over 1 second:

animate('x', 0, 1000);

To move the square vertically up the canvas, we can use the below code that will alter the y value over 1 second:

animate('y', 0, 1000);

To move the square diagonally we can alter both the x and y simultaneously:

animate('x', 50, 1000);
animate('y', 50, 1000);

Extending It

What if you wanted to add keyboard controls to the box? This is slightly more complex, but shouldn't take too long to get your head around.

The idea is to start an animation for the square to move when the key is pressed down, but stop it when the key is no longer being pressed. We'll be attaching keydown and keyup events in order to listen for these keys being pressed.

document.body.addEventListener('keydown', function(e) {

});

document.body.addEventListener('keyup', function(e) {

});

We can use the event variable to generate information about the movement such as the property to animate and by how far. However, we'll be needing this in both the keydown and keyup events, so we'll put this in a separate function.

var meta = function(e) {

};

In this meta function, we need to decide which direction the square is going, and by how far. This is actually relatively easy, once you know which key codes correlate to which direction. You can find a full reference for these key codes here, but to save time, I'll give you the relevant ones here.

So now all we need to do is to check against the key code (contained in e.which) and then we'll have all the information we need. This function checks the event variable to see if we're going up, down, left, or right. We use a 'multiplier' variable, to make our distance negative if we're going left or up. We'll also use a prop variable to change the property we're animating if we're going up, or down.

var meta = function(e) {
    // Set some initial variables
    var distance = 100;
    var prop = 'x';
    var mult = 1;

    // Just return false if the key isn't an arrow key
    if (e.which < 37 || e.which > 40) {
        return false;
    }

    // If we're going left or up, we want to set the multiplier to -1
    if (e.which === 37 || e.which === 38) {
        mult = -1;
    }

    // If we're going up or down, we want to change the property we will be animating. 
    if (e.which === 38 || e.which === 40) {
        prop = 'y';
    }

    return [prop, mult * distance];
}

Inside the keydown event, we can grab the meta information using the meta function, and then animate the square based on that information. In the key up event, we'll stop the animation.

document.body.addEventListener('keydown', function(e) {
    var info = meta(e);
    if (info) {
        e.preventDefault();
        animate(info[0], square[info[0]] + info[1], 1000);
    };
});

document.body.addEventListener('keyup', function(e) {
    var info = meta(e);

    if (info) {
        e.preventDefault();
        animate(info[0], square[info[0]], 1000);
    };
});

After adding the meta function, and the two event listeners, you should be able to move the square around the canvas using the arrow keys.

Conclusion

This should be all you need to kickstart your shiny new HTML 5 game. If you would like to see all the techniques discussed here, you can go check out this JSFiddle that I wrote during the writing of this article.

Tags: JavaScript, Canvas