Create a Sprite Animation with HTML5 Canvas and JavaScript
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".
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.
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 |
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 |
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!