Using canvas to do bitmap sprite animation in JavaScript

August 21, 2009 – 7:02 pm Tags: , ,

Have you ever thought about writing a game? If you have, you’ve probably wondered how to render animations for your game characters. In this post, I’ll show you how you can use JavaScript to do time-based sprite animations, drawing them on canvas – vital if you want to do a game. You can also apply the same techniques I’ll show in other languages, such as ActionScript.

What is “time-based” animation?

We all know what animation means, but what does time-based add?

Let’s imagine we have a game. Our game runs at 60 frames per second (fps) on a powerful PC, and 20 fps on a slow PC.

Animation that isn’t time-based is frame-based. In this case, a frame means a “cycle” in the game: In each frame you typically calculate things like movement or AI, and draw things on the screen.

Let’s say we have a walk animation which changes every frame. This means that on the fast PC the animation is much faster than on the slow PC, since the framerate is much higher.

Time-based animation calculates how long each frame in your game takes, and uses this to calculate how long an animation should take. By using time instead of frames, we can achieve exactly the same speed on both the fast and slow PCs.

There is also a term “time-based movement”. This applies the same principle to movement of characters and other things on the screen so that everything moves at the same speed no matter what frame rate.

The infrastructure

There is some infrastructure we need before we can actually start animating things.

  • We need a timer which can calculate how long each frame takes
  • We need a sprite sheet object, which we can use to keep track of different sprites in a big bitmap
  • Lastly, we need an animation object which handles choosing the correct sprite from a spritesheet and such.

In addition to these, in a game you would also have things like animation and spritesheet loaders that load spritesheet and animation definitions from files so you won’t have to modify your code to change an animation. However, these are outside the scope of this post – if you’d like to see a post about loading this kind of things from files, leave a comment.

First, let’s look at making the class which will take care of timing the frames.

Creating a frame timer

The basic idea of a frame timer is that it should be called once every frame, usually after the processing is complete. At that point, it compares how much time has passed since the previous call. We can then use this value on the next frame to make a good guess of how long it’ll take this time.

To determine the time on each frame, we’ll use the Date object.

var FrameTimer = function() {
    this._lastTick = (new Date()).getTime();
};
 
FrameTimer.prototype = {
    getSeconds: function() {
        var seconds = this._frameSpacing / 1000;
        if(isNaN(seconds)) {
            return 0;
        }
 
        return seconds;
    },
 
    tick: function() {
        var currentTick = (new Date()).getTime();
        this._frameSpacing = currentTick - this._lastTick;
        this._lastTick = currentTick;
    }
};

This simple looking class simply tracks time spent during two “ticks”. With this we track the actual time spent between two frames, so that we can keep the animation’s speed constant in real time instead of application speed.

Using this object is simple:

var timer = new FrameTimer();
//Tick once to initialize the time
timer.tick();
 
//Now do processing
while(true) {
  //do things based on time
  work(timer.getSeconds());
 
  timer.tick();
}

Sprite sheet and animation objects

To make our life a bit easier in the code, let’s abstract the code which calculates the exact location of a specific sprite on a spritesheet. Let’s also write a class which abstracts the animation and handling which frame should be displayed.

Since we’ll need the spritesheet class for the animation class, let’s look at that one first.

The idea is that we can create a spritesheet object that helps us in drawing sprites from a big sheet. As you know, to draw a specific sprite from a sheet, we need to know the location of it on the sheet – locating a specific sprite will be the main job of the spritesheet object, so that we won’t have to think of it all the time.

var SpriteSheet = function(data) {
    this.load(data);
};
 
SpriteSheet.prototype = {
    _sprites: [],
    _width: 0,
    _height: 0,
 
    load: function(data) {
        this._height = data.height;
        this._width = data.width;
        this._sprites = data.sprites;
    },
 
    getOffset: function(spriteName) {
        //Go through all sprites to find the required one
        for(var i = 0, len = this._sprites.length; i < len; i++) {
            var sprite = this._sprites[i];
 
            if(sprite.name == spriteName) {
                //To get the offset, multiply by sprite width
                //Sprite-specific x and y offset is then added into it.
                return {
                    x: (i * this._width) + (sprite.x||0),
                    y: (sprite.y||0),
                    width: this._width,
                    height: this._height
                };
            }
        }
 
        return null;
    }
};

The basic usage of this class is you create a data-structure which contains the spritesheet definition, and pass it to the object, such as this:

var sprites = new SpriteSheet({
    width: 32,
    height: 32,
    sprites: [
        { name: 'stand' },
        { name: 'walk_1', x: 0, y: 1 },
        { name: 'walk_2', x: 0, y: 1 },
    ]
});

The definition is simple: First, we define the width and height of a sprite, and then we define a name and x/y offsets for each sprite in the sheet.

When we apply this with an image which has three sprites, each 32×32 in size, we can simply tell the spritesheet that we want the sprite called ‘walk_1′, and we get a nice object with the X and Y positions to draw it from the sheet. We’ll see this in action soon!

The animation class

Now that we have the spritesheet stuff done, we can do the last in the line: The animation class.

The animation class takes a definition, similar to how the spritesheet took, and then handles various things based on that. For example, as each animation comprises of specific frames, it calculates how long a frame has been visible, and changes to the next frame when it has been visible long enough.

On with the code:

var Animation = function(data, sprites) {
    this.load(data);
    this._sprites = sprites;
};
 
Animation.prototype = {
    _frames: [],
    _frame: null,
    _frameDuration: 0,
 
    load: function(data) {
        this._frames = data;
 
        //Initialize the first frame
        this._frameIndex = 0;
        this._frameDuration = data[0].time;
    },
 
    animate: function(deltaTime) {
        //Reduce time passed from the duration to show a frame        
        this._frameDuration -= deltaTime;
 
        //When the display duration has passed
        if(this._frameDuration <= 0) {
            //Change to next frame, or the first if ran out of frames
            this._frameIndex++;
            if(this._frameIndex == this._frames.length) {
                this._frameIndex = 0;
            }
 
            //Change duration to duration of new frame
            this._frameDuration = this._frames[this._frameIndex].time;
        }
    },
 
    getSprite: function() {
        //Return the sprite for the current frame
        return this._sprites.getOffset(this._frames[this._frameIndex].sprite);
    }
}

The comments should explain most of this class. The main point is that with this, we are done!

Now we only need to look at the animation definition format, and then we can combine these three into the most amazing animation machine the internet has ever seen… or something like that anyway.

var walk = new Animation([
    { sprite: 'walk_1', time: 0.2 },
    { sprite: 'stand', time: 0.2 },
    { sprite: 'walk_2', time: 0.2 },
    { sprite: 'stand', time: 0.2 }
], sprites);

Here we define a walk animation. The animation is defined by simply giving the object an array, which contains objects for each frame, defining the name of the sprite and the duration of the frame. The variable sprites in the end is the spritesheet we defined in the previous example.

Putting it all together

Now, let’s put all this together!

I have a spritesheet lying around from some old projects: kunio.gif. Let’s animate this.

You may be familiar with this character – He is Kunio from an old NES game known as Downtown Nekketsu Monogatari, released in english as River City Ransom, and he’s been featured on various flash-animations as well.

I’ve used this sprite in a few other projects:

But enough of that, let’s write a quick HTML page to put our code to work:

<!DOCTYPE html>
<html>
<head>
    <title>Canvas Spritesheet Animation</title>
    <script type="text/javascript" src="js/FrameTimer.js"></script>
    <script type="text/javascript" src="js/SpriteSheet.js"></script>
    <script type="text/javascript" src="js/Animation.js"></script>
    <script type="text/javascript">
        window.onload = function() {
            var timer = new FrameTimer();
            timer.tick();
 
            var sprites = new SpriteSheet({
                width: 32,
                height: 32,
                sprites: [
                    { name: 'stand' },
                    { name: 'walk_1', x: 0, y: 1 },
                    { name: 'walk_2', x: 0, y: 1 },
                ]
            });
            var ctx = document.getElementById('canvas').getContext('2d');
            var walk = new Animation([
                    { sprite: 'walk_1', time: 0.2 },
                    { sprite: 'stand', time: 0.2 },
                    { sprite: 'walk_2', time: 0.2 },
                    { sprite: 'stand', time: 0.2 }
            ], sprites);
            var kunioImage = new Image();
 
            kunioImage.onload = function() {
                setInterval(function(){
                    walk.animate(timer.getSeconds());
                    var frame = walk.getSprite();
                    ctx.clearRect(0, 0, 300, 300);
                    ctx.drawImage(kunioImage, frame.x, frame.y, 32, 32, 0, 0, 32, 32);
                    timer.tick();
                }, 5);
            };
 
            kunioImage.src = 'img/kunio.gif';
        };
    </script>
</head>
<body>
<h1>Canvas sprite animation demo</h1>
<canvas id="canvas" width="300" height="300"></canvas>
</body>
</html>

Here it is. Our great animation system!

A live example and source code

You can see this code in action by clicking here

As usual, don’t expect it to work in Internet Explorer. It has been tested to work in Opera 10 and Firefox 3, probably works in other canvas-supporting browsers as well.

Source:

Conclusion

Animating a spritesheet with canvas is quite simple. This is why JavaScript is quickly becoming a powerful platform for small games in my opinion – very easy to use, and quite ubiquitous.

You can use this same approach to animation in other languages too – For example, the Flex example I linked uses a very similar approach.

Share this:

RSS feed Subscribe to my RSS feed

About the author

Jani is a 15 year veteran of the software industry. He's currently available for consulting

  1. 27 Responses to “Using canvas to do bitmap sprite animation in JavaScript”

  2. Great tutorial Jani!
    Ya right now for flash I just use a moveclip that holds animations in each frame but if I ever try flex I’ll use the timer method :)

    By CodeJustin on Aug 22, 2009

  3. You might want to take a look at the tricky behavior of Firefox and Safari when they render an animated GIF through canvas (http://ernestdelgado.com/public-tests/gifoncanvas/)
    and see if your technique could take any advantage of that behavior.

    By Ernest on Aug 23, 2009

  4. Ernest, otherwise yes, but I’m an Opera user and it doesn’t really work in Opera ;)

    Also, all things considered, it has a drawback: You cannot stop the animation, or control it more precisely. You can have full control over when the frame is changed etc. with the approach I’ve shown here.

    By Jani Hartikainen on Aug 23, 2009

  5. How can I save image from canvas to file?

    By Gairon on Sep 21, 2010

  6. Hi,

    This is such a good tutorial.
    I confess your coding style is a little complex for my tastes but I love what you’ve done. So much so that I’ve moved away from DIVs and IMAGES forever !

    BUT.. my first attempts at drawImage() have produced unexpected results. The scale is all wrong !

    Can you help ?

    The code is here: http://www.wilfscorner.co.uk/sandpit/1/

    By Wilf on Oct 15, 2010

  7. Wilf, the reason it’s appearing distorted is because you only define the CSS-width/height for the canvas. You need to also define width and height on the element itself, because that defines the resolution of the canvas (or something like that anyway)

    This should fix it:

    <canvas width="480" height="480"></canvas>

    (Defining gl.canvas.width and …height should also work)

    By Jani Hartikainen on Oct 18, 2010

  8. Jani, thanks so much.
    I have this working nice just now.
    Am looking to rotate individual images (as if they were sprites) on the canvas.
    The only methods I have found just now appear to rotate the canvas context rather than the images displayed on the canvas.
    Do you know of a way to rotate images independently of one another on the canvas ?
    Once again many thanks.
    Wilf

    By Wilf on Oct 18, 2010

  9. save(), rotate(), draw(), restore() – should do the trick :) (do this for each thing you draw)

    By Jani Hartikainen on Oct 18, 2010

  10. Great example, though preserve game logic and animation quality at very low frame rates by never ‘throwing away time’. Helps to have your game still playable / consistent on e.g. mobile browsers:

    Change to while() and += in the animation code, the entire elapsed time will run through animation frames even if more than one change occurs:

            //When the display duration has passed
            while(this._frameDuration <= 0) {
    ...
    
                //Change duration to duration of new frame
                this._frameDuration += this._frames[this._frameIndex].time;
            }

    By Vincent Scheib on Jan 28, 2011

  11. Hey. Great post. It has helped me a lot in finding my bearings for building a canvas game. do you have any thoughts (or maybe a post? :) ) on having a background and sliding it across to simulate movement?

    By Jeff on Mar 5, 2011

  12. How to i use bigger gifs? i’ve changed the source of the image to a bigger image and i only see a small quadrant. i can make the image bigger and smallbut i still only see the same quadrant. i tried adjusting the height ad width variable but it still only shows the same area.

    By jake on Nov 23, 2011

  13. Did you remember to change the drawImage line as well? You need to change both the sprite height/width and the line which draws the image

    By Jani Hartikainen on Nov 23, 2011

  14. yeah i was changing the drawImage line but there is rules to changing it. So after i made everything fit the rules it worked perfectly ;)

    By jake on Nov 23, 2011

  15. Hi Jani!

    I’m currently experimenting on a small javascript game. I’m new to the idea of time-based animation, thank you for introducing it.

    BTW, I’m having this dilemma between using and tag or using the to animate sprites.

    Is there a big advantage on using ? Thank you :)

    By Karl Paragua on Jan 21, 2012

  16. @Karl it seems the comment system may have eaten parts of your comment, it’s a bit hard to decipher :)

    By Jani Hartikainen on Jan 21, 2012

  17. @Jani

    I didn’t notice that my comment was obscured. BTW, my question, is there a big advantage on using Canvas drawing over DIV and IMG tags? Thank you! :)

    By Karl Paragua on Jan 24, 2012

  18. With canvas you get direct pixel manipulation so you can do all sorts of effects not possible with divs/images. You also can scale, rotate etc. the graphics you draw.

    By Jani Hartikainen on Jan 24, 2012

  19. How do you get to the second line of sprites?

    { name: ‘stand’ },
    { name: ‘walk_1′, x: 0, y: 1 },
    { name: ‘walk_2′, x: 0, y: 1 },
    { name: ‘frame_04′, x: 0, y: 1 },
    { name: ‘jump’, x: 0, y: 1 },
    { name: ‘punch_1′, x: 0, y: 1 },
    { name: ‘punch_2′, x: 0, y: 1 },
    { name: ‘punch_3′, x: 0, y: 1 },
    { name: ‘hit_1′, x: 0, y: 1 }, <- VALUES?
    { name: 'hit_2', x: 0, y: 1 }, <- VALUES?
    { name: 'die', x: 0, y: 1 }, <- VALUES?

    By randelreiss on Jan 28, 2012

  20. Ah, figured it out, but this seems a little messy:

    { name: ‘stand’ },
    { name: ‘walk_1′, x: 0, y: 1 },
    { name: ‘walk_2′, x: 0, y: 1 },
    { name: ‘frame_04′, x: 0, y: 1 },
    { name: ‘jump’, x: 0, y: 1 },
    { name: ‘punch_1′, x: 0, y: 1 },
    { name: ‘punch_2′, x: 0, y: 1 },
    { name: ‘punch_3′, x: 0, y: 1 },
    { name: ‘hit_1′, x: -256, y: 32 },
    { name: ‘hit_2′, x: -256, y: 32 },
    { name: ‘die’, x: 256, y: 32 },

    By randelreiss on Jan 28, 2012

  21. This is a great tutorial, but Im having a hard time injecting code to stop or manipulate which frame is seleted – basically I want to control the current frame or at the very least stop the animation at a given time.

    Any help would be great, thanks

    By Eric on Apr 3, 2012

  22. Hi!

    There’s a small bug in your code.

    It’s not

    { name: 'walk_1', x: 0, y: 1 },
    { name: 'walk_2', x: 0, y: 1 },

    But rather:

    { name: 'walk_1', x: 32, y: 0 },
    { name: 'walk_2', x: 64, y: 0 },

    And you may add the idea of “offset” or defining image width and height so there’s never a problem with looking for the right position of the sprite.

    By Olivier Pons on Apr 29, 2013

  23. I’m sorry to re-post, but in your “animate” function, if there are more than one frame “lost”, you may run into problem. Your code just test if only “one” frame is lost.

    //When the display duration has passed
    if(this._frameDuration &lt;= 0) {
      //Change to next frame, or the first if ran out of frames
      this._frameIndex++;
      ....
    }

    You may do a “while”, something like:

    //When the display duration has passed
    while(this._frameDuration &lt;= 0) {
      ....
    }

    (Find a way so that you skip as many frames as needed, this may need some code rework)

    ——–
    Anyway, congratulations, your code is a very good start for making a good game.

    By Olivier Pons on Apr 29, 2013

  24. I hope my code may help some people having hard times with a few problems:
    Here’s how I’ve implemented it: for SpriteSheet.js:

    var SpriteSheet = function(data) {
        this.load(data);
    };
     
    SpriteSheet.prototype = {
        _sprites: {},
        _width: 0,
        _height: 0,
    	_bounds: {
    		offsetX: 0,
    		offsetY: 0,
    		width: 0,
    		height: 0
    	},
     
        load: function(data) {
            this._width   = data.width;
            this._height  = data.height;
            this._bounds  = data.bounds;
     
    		// pre-caculate array with proper index
    		// and pre-calculate x &amp; y of each sprite
            for(var i in data.sprites) {
    			var idx = data.sprites[i].name;
    			this._sprites[idx] = {
    				// (!) TODO: x &amp; y: test if out of bounds
    				x:	this._bounds.offset.x +
    					((data.sprites[i].tile.x||0) * this._width),
    				y:	this._bounds.offset.y +
    					((data.sprites[i].tile.y||0) * this._height)
    			};
    		}
        },
     
        getOffset: function(spriteName) {
            //Go through all sprites to find the required one
    		var sp = this._sprites[spriteName];
    		if (typeof sp === "undefined") {
    			return {
    				x: 0,
    				y: 0,
    				width: this._width,
    				height: this._height
    			};
    		} else {
    			return {
    				x: sp.x,
    				y: sp.y,
    				width: this._width,
    				height: this._height
    			};
    		}
        }
    };

    And for the index.html:

     
     
     
        Animation
     
     
     
     
    		var myTimer;
            window.onload = function() {
                var timer = new FrameTimer();
                timer.tick();
     
                var mySpriteSheet = new SpriteSheet({
                    sprites: [
                        { name: 'stand',  tile: {x: 0, y: 0 } },
                        { name: 'walk_1', tile: {x: 1, y: 0 } },
                        { name: 'walk_2', tile: {x: 2, y: 0 } },
                    ],
                    width: 32,
                    height: 32,
    				bounds: {
    					offset: {x: 0, y: 0 },
    					width: 256,
    					height: 64
    				}
                });
                var ctx = document.getElementById('canvas').getContext('2d');
                var walk = new Animation([
                        { sprite: 'walk_1', time: 0.3 },
                        { sprite: 'stand', time: 0.3 },
                        { sprite: 'walk_2', time: 0.3 },
                        { sprite: 'stand', time: 0.3 }
                ], mySpriteSheet);
                var kunioImage = new Image();
     
                kunioImage.onload = function() {
                    myTimer = setInterval(function(){
                        walk.animate(timer.getSeconds());
                        var frame = walk.getSprite();
                        ctx.clearRect(0, 0, 300, 300);
                        ctx.drawImage(kunioImage, frame.x, frame.y, 32, 32, 0, 0, 32, 32);
                        timer.tick();
    					//clearInterval(myTimer);
                    }, 5);
                };
     
                kunioImage.src = 'img/kunio.gif';
            };

    By Olivier Pons on Apr 29, 2013

  1. 4 Trackback(s)

  2. Dec 16, 2009: Rewriting TankWar: Assessing the damage | CodeUtopia - The blog of Jani Hartikainen
  3. Feb 13, 2010: Rewriting TankWar: Assessing the damage « Evanwilliamsconsulting.com Blogs
  4. Jan 30, 2012: 如何做html5山寨版愤怒的小鸟 | 博客
  5. Jan 30, 2012: 如何做html5山寨版愤怒的小鸟 | 游戏开发

Post a Comment

You can use some HTML (a, em, strong, etc.). If you want to post code, use <pre lang="PHP">code here</pre> (you can replace PHP with the language you are posting)