Create a Sprite Animation with HTML5 Canvas and JavaScript

by William Malone
HTML5 Sprite

Sprite animations can be drawn on HTML5 canvas and animated through JavaScript. Animations are useful in game and interactive application development. Several frames of an animation can be included in a single image and using HTML5 canvas and JavaScript we can draw a single frame at a time.

This tutorial will describe how HTML5 sprite animations work. We will go step by step through the process of creating a sprite animation. At the end of the this article we will use the animation we created in a simple HTML5 game.

The example code is based off the game development framework: BlocksJS. The full game framework with additional features can be downloaded from BlocksJS on GitHub. The source code for the examples in the article is available at the end.

What is a Sprite Animation?

Two dimensional frame-based animation can be achieved on HTML5 canvas by rendering an area of a single image on a given interval. The following five frame animation is rendered on a HTML5 canvas at one frame per second (1 fps).

Using the drawImage method of the canvas context we can change the source position to only draw a cropped portion of one image called a "sprite sheet".

HTML5 Sprite

What is a Sprite Sheet?

HTML5 canvas animation sprite sheets are fundamentally the same as CSS sprite sheets. A sprite sheet consists of muliple frames in one image. The following sprite sheet has 10 frames. The width of the image is 460 pixels. Therefore the frame width is 460/10 or 46 pixels.

HTML5 Sprite

Now Let's Create a Sprite Animation

Let's start by loading the sprite sheet image for the coin animation. Create a new Image object and then set its src property to the filename of the image which will load the image.

var coinImage = new Image();
coinImage.src = "images/coin-sprite-animation.png";

Next we define the sprite object so we can create one (or more later). Invoking the object will simply return an object with three public properties.

function sprite (options) {
				
    var that = {};
					
    that.context = options.context;
    that.width = options.width;
    that.height = options.height;
    that.image = options.image;

    return that;
}

Grab access to the canvas element using getElementById and then set its dimensions. We will need the canvas's context to draw to later.

<canvas id="coinAnimation"></canvas>
var canvas = document.getElementById("coinAnimation");
canvas.width = 100;
canvas.height = 100;

Now we can create a sprite object which we will call coin. Using the options parameter we set properties of the object which will specify: the context of the canvas on which the sprite will be drawn, the sprite dimensions, and the sprite sheet image.

var coin = sprite({
    context: canvas.getContext("2d"),
    width: 100,
    height: 100,
    image: coinImage
});

DrawImage Method

The key to creating sprites from one image is that the context's drawImage method allows us to render a cropped section of the source image to the canvas.

context.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh)

img Source image object Sprite sheet
sx Source x Frame index times frame width
sy Source y 0
sw Source width Frame width
sh Source height Frame height
dx Destination x 0
dy Destination y 0
dw Destination width Frame width
dh Destination height Frame height
HTML5 drawImage example

We will use the drawImage method in our sprite's render method to draw one frame at a time.

Render

Time to draw to the canvas. The sprite function will need a render method that invokes the drawImage method on the canvas' context. The parameters specify the source image and the bounding rectangle dimensions and position of the source sprite sheet and the destination canvas context.

function sprite (options) {

...
				
    that.render = function () {

        // Draw the animation
        that.context.drawImage(
           that.image,
           0,
           0,
           that.width,
           that.height,
           0,
           0,
           that.width,
           that.height);
    };
    
...

}

Oh, and call the render method.

coin.render();

We have drawn the coin, but where is the animation?

Update

We need a method so we can update the position of the sprite sheet's bound rectangle. Let's call it... update. We need also need to keep track of where the animation is so let's create some properties:

frameIndex
The current frame to be displayed
tickCount
The number updates since the current frame was first displayed
ticksPerFrame
The number updates until the next frame should be displayed

We could just increment the frame index every time we call the update method, but when running a game at 60 frames per second we might want the animation to run at a slower fps. Tracking the ticks let's use delay the animation speed. For example we could run the sprite animation at 15 fps by setting the ticksPerFrame to 4 on game loop running at 60 fps.

Each time the update method is called, the tick count is incremented. If the tick count reaches the ticks per frame the frame index increments the tick count resets to zero.

function sprite (options) {
                    
    var that = {},
        frameIndex = 0,
        tickCount = 0,
        ticksPerFrame = options.ticksPerFrame || 0;

...

    that.update = function () {

        tickCount += 1;
			
        if (tickCount > ticksPerFrame) {
        
        	tickCount = 0;
        	
            // Go to the next frame
            frameIndex += 1; 
        }
    }; 
    
...

}

The render method can now move the bounding rectangle of the source sprite sheet based on the frame index to be displayed by replacing the sprite width with the frame width: that.width is replaced with that.width / numberOfFrames.

function sprite (options) {

...

    that.render = function () {

        // Draw the animation
        that.context.drawImage(
           that.image,
           frameIndex * that.width / numberOfFrames,
           0,
           that.width / numberOfFrames,
           that.height,
           0,
           0,
           that.width / numberOfFrames,
           that.height);
    };   
    
...

}

This works until the frame index is greater than the number of frames. We need a new property numberOfFrames and a conditional to ignore out of range values.

function sprite (options) {
                    
    var that = {},
        frameIndex = 0,
        tickCount = 0,
        ticksPerFrame = 0,
        numberOfFrames = options.numberOfFrames || 1;

...

    that.update = function () {

        tickCount += 1;
			
        if (tickCount > ticksPerFrame) {
        
            tickCount = 0;
        	
            // If the current frame index is in range
            if (frameIndex < numberOfFrames - 1) {	
                // Go to the next frame
                frameIndex += 1;
            }	
        }
    };
    
...

}

We want our coin to keep spinning after the first go around. We will need a new loop property and a conditional which resets the frame index and tick count if the last frame.

function sprite (options) {

...

    that.loop = options.loop;

    that.update = function () {

        tickCount += 1;
			
        if (tickCount > ticksPerFrame) {
        
            tickCount = 0;
        
            // If the current frame index is in range
            if (frameIndex < numberOfFrames - 1) {	
                // Go to the next frame
                frameIndex += 1;
            } else if (that.loop) {
                frameIndex = 0;
            }
        }
    };
    
...

}

But wait! Without a loop the update will only run once. We need to run the update on a loop...

RequestAnimationFrame

We use requestAnimationFrame (See Paul Irish's post for a requestAnimationFrame polyfill for support in older browsers) to update and render the sprite at the same rate that the browser repaints.

function gameLoop () {

  window.requestAnimationFrame(gameLoop);
  
  coin.update();
  coin.render();
}

// Start the game loop as soon as the sprite sheet is loaded
coinImage.addEventListener("load", gameLoop);

Close, but something is not quite right.

ClearRect Method

The clearRect method allows us to clear a region of the destination canvas between animation frames.

context.clearRect(x, y, width, height)

x Clear rectangle x position
y Clear rectangle y position
width Clear rectangle width
height Clear rectangle height
HTML5 drawImage example
that.render = function () {
					
    // Clear the canvas
    context.clearRect(0, 0, that.width, that.height);

        // Draw the animation
        that.context.drawImage(
           that.image,
           frameIndex * that.width / numberOfFrames,
           0,
           that.width,
           that.height,
           0,
           0,
           that.width,
           that.height);
    };   
};

Clearing the canvas between frames gives us our complete sprite animation.

Sprite Animation Demo

Demo can be loaded here or downloaded at the end of the article.

Coin Tap Game

Now that we have learned how to create a sprite animation on HTML5 canvas we can use the game loop and animation to create a game. Let's keep the game simple: tap a coin to get points.

To create our game we need to add a couple of event listeners for desktop (mousedown) and mobile (touchstart) and then use a simple circle collision detection to determine if the coin is tapped. If a coin is hit we remove the coin and create a new one resulting in a score based on the coin size. When creating new coins the size and speed of spin are randomized.

We are drawing more than one sprite on the canvas now, so instead of clearing the canvas when we render a sprite we need to we need to clear the canvas at the beginning the game loop. If we didn't we would clear the all the previous sprites.

With these updates we can create our Coin Tap Game; let's give it a try!

0

Download Source Code

Share This Article

Related Articles