Spicy Yoghurt | Last updated: 9 March 2019 | HTML5 game tutorial

Create a smooth canvas animation

Create an animation on the HTML5 canvas and make it run smooth, no matter the frame rate. Learn how to apply time and easing functions. By the end of this tutorial you can create a basic animation with the use JavaScript.

This tutorial requires the HTML5 file with the game loop, you have created in the previous tutorial. If you skipped that step, you can learn how to set-up a game loop here or download the HTML5 file with game loop here.

Draw objects dynamically on the canvas

The draw() function in the previous tutorial is very static. It only draws one rectangle at the same position, over and over again. Let's make things a bit more interesting. Change the old draw() function to this new one:

                
                    function draw() {
                        context.fillStyle = '#ff8080';
                        context.fillRect(rectX, rectY, 150, 100);
                    }
                
            
As you can see, the draw operation fillRect() is now using variable arguments as input. If you change the values of rectX and rectY, you should get a moving rectangle.

Lets mix this up with the game loop. With the following code you can update the position of the rectangle:

                            
                var rectX = 0;
                var rectY = 0;

                function gameLoop(timeStamp) {
                    // Update game objects in the loop
                    update();
                    draw();

                    window.requestAnimationFrame(gameLoop);
                }

                function update() {
                    rectX += 1;
                    rectY += 1;
                }
            
            

When to update the position of your game objects?

A simple update() function adds 1 to the coordinates of the rectangle. Every frame the update() function is called in the gameLoop(), moving the rectangle. It is important to perform the update() before the draw(), so that you'll always draw the latest state of your game to the canvas.

Remember this when you start to add other tasks to your game loop. Always update the state of your game objects first and, as a final action, draw everything to the screen. For now, your game loop looks like this:
A basic game loop without clear

A blur of repeated graphics

You should expect to see the rectangle move in a slanted line, from the upper left corner to the lower right. Here is what you get when you run the code: What is happening here? It looks like an endless repetition of rectangles, creating a big blur. The rectangle is drawn on the canvas, but the previous rectangle is never removed. Why is that?

How does the canvas work?

Well, the canvas acts as a drawing board. You can draw on it, change position of the rectangle, and draw it again. Do this quickly enough and you will get the perception of motion.

But when you forget to clear the canvas, you will draw each drawing on top of the previous. Creating a blur of drawings, like in the example above.

You can make use of this effect to create some interesting graphics, but that's not what you need right now. You want to animate the rectangle and not create a blur.

Clear the canvas before drawing

To solve this problem, you'll have to clear the canvas before every new drawing operation. So every time you draw, you start with a clean slate. This will prevent the blur effect.

Add clearRect() to the draw() function to always clear the canvas before drawing:

                
                    function draw() {
                        // Clear the entire canvas
                        context.clearRect(0, 0, canvas.width, canvas.height);

                        context.fillStyle = '#ff8080';
                        context.fillRect(rectX, rectY, 150, 100);
                    }
                
            
The clearRect() function clears a part of the canvas. In this case, it is set to clear an area covering the entire canvas. Starting from the upper left corner at 0,0 , continuing for canvas.width and canvas.height.

With this new clear method in place, your game loop looks like this: A basic game loop with update, clear and draw

When you run the new code the rectangle is animated correctly. See the example below:

Effects of dynamic frame rate on the animation

Right now, the animation is really simple and runs smooth on about every device. But what if the animation was a lot more complex, like when running a game with many different animated objects?

Your computer or mobile device might not be able to keep up and delays some frames. Later, when there is less stress on the system, it might speed up again. The motion of your animation will be held back or speed up too and you don't want that to happen. You want to move you objects with a constant speed, no matter the frame rate, so your animation will always look the same on any kind of hardware or device.

When running older games you can see this kind of effects very clearly. These games do not compensate for different frame rates and were designed for old hardware with a slow clock speed. When you run a game like that with modern hardware, it will run super-fast and reach a very high frame rate. Every motion is now performed with lightning speed. When you press an arrow key to move, you'll end up at the end of the screen in a flash.

The movement speed of objects doesn't seem to take the frame rate into account. But how can you fix this for your own animation or game?

Handle dynamic frame rates

To compensate for the effects of a dynamic frame rate, you'll want to include time as a factor in your animation. This way, it's no longer the frame rate (and hardware) that decides the speed of your game, but it's time. See the following code:


                var oldTimeStamp = 0;
                var movingSpeed = 50;

                function gameLoop(timeStamp) {
                    // Calculate how much time has passed
                    var secondsPassed = (timeStamp - oldTimeStamp) / 1000;
                    oldTimeStamp = timeStamp;

                    // Pass the time to the update
                    update(secondsPassed);
                    draw();

                    window.requestAnimationFrame(gameLoop);
                }

                function update(secondsPassed) {
                    // Use time to calculate new position
                    rectX += (movingSpeed * secondsPassed);
                    rectY += (movingSpeed * secondsPassed);
                }
            
At the start of the gameLoop() the number of passed seconds gets calculated. This value is passed to the update() function. There it is used to calculate the new position of the rectangle. Using time as a factor.

Move relative to time

Let's explain this some more. When your game runs at 60fps, that's roughly 0.0167 seconds per frame. This means that when you want to move an object with, let say, 50 pixels per second, you have to multiply 50 with the number of seconds that have passed since the last frame. A game running at 60 fps will move the object 0.835 pixels per frame. That's what's happening in the update() function.

So, when the frame rate increases or decreases, the movement speed will too. No matter how much time has passed, your objects will always move at the desired speed. This makes the animation more suitable for different kinds of hardware, with different frame rates. Of course, a lower frame rate will make the animation look choppy, but the motion stays the same.

Easing the motion of an animation

Now that you can use time as a factor, you can do some fun things with your animation. Remember the update() function where you update the position of your game object? Well, you can make that a bit more interesting by applying an ease to the animation.


            var timePassed = 0;

            function update(secondsPassed) {

                timePassed += secondsPassed

                // Use different easing functions
                // for different effects.
                rectX = easeInOutQuint(timePassed, 50, 500, 1.5);
                rectY = easeLinear(timePassed, 50, 250, 1.5);
            }

            // Example easing functions
            function easeInOutQuint (t, b, c, d) {
                if ((t /= d / 2) < 1) return c / 2 * t * t * t * t * t + b;
                return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
            }

            function easeLinear (t, b, c, d) {
                return c * t / d + b;
            }
    
This simple example uses a quintic ease and applies it to the x-position. The y-position is updated in a linear fashion. Here is the result: The easing functions might look difficult to understand, but the good news is you don't really have to. As long as you know what arguments to pass, you can use them. This is all explained at the animation tool for easing functions.

Try the animation tool for easing functions

You can make a lot of interesting movements by just applying an easing to an motion. On the Spicy Yoghurt website there is a tool to visualize easing functions. This will help you to pick the right function and understand easing better.
Your browser does not have canvas support Hand icon

Animation Tool

Easing functions made easy

Eased motions are a great way to spicy up your animations. Use this tool to visualise easing functions and make your animations come to life.
Use the animation tool to learn more about easing and what arguments to pass to the easing functions. Play around in the update() function to create new movement effects. That's all for now on easing.

Final code

You managed to move and animate objects on the HTML5 canvas in a smooth manner and learned about the effects frame rate has on animations. You also learned how to include time as a factor in your animation and use easing functions. You can download the final code here. Feel free to ask questions in the comments.

In the next step of the tutorial, you'll be learning how to create multiple objects, detect collisions and make objects interact with each other.

Leave a comment

<