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.
Creating web animations
The basics of creating an animation you'll learn here in this tutorial are a key component for creating games, but you can also apply the same principles to create web animations.
Back in the days, people used to create web animations using Adobe Flash. They were very commonly used for banner ads. SWF files would run inside the browser, using the Flash Player plugin. For more decorative purposes, GIFs were used. You can read more on the use and history of web animations here.
Now, with the ending Flash Player support by browsers, HTML5 canvas and CSS animations have taken over. CSS or JavaScript can be used to make simple animations by manipulating HTML elements. For more complex animations, or games, you can use the canvas techniques explained in this tutorial.
Now, let's continue and learn how to create your first canvas animation.
Draw objects dynamically on the canvas
In the previous tutorial you've learned how to draw repeatedly on the canvas. But the used draw() function 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.
Let's mix this up with the game loop. With the following code you can update the position of the rectangle:
let rectX = 0;
let 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 slightly changes the coordinates of the rectangle. Every frame the update() function is called in the gameLoop() and this will make the rectangle move. 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 blur of repeated graphics
When executing the code, 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:
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:
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:
let secondsPassed = 0;
let oldTimeStamp = 0;
let movingSpeed = 50;
function gameLoop(timeStamp) {
// Calculate how much time has passed
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 50 pixels per second, you have to multiply 50 by 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.
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 displacement of the objects stays the same.
Limit the time skip
In some special cases, the movement correction to make objects always move at the same speed becomes so large, it will eventually break your game. Imagine hardware that is infinitely slow, your movement speed will be huge to make up for the gap in time between your previous and current frame. Your objects would have to move with such a big step each frame that game logic becomes unstable.
You can easily replicate this behaviour by running your game in a browser tab and switching to another random tab. The moment you switch back to your first tab, the time between the last two frames is huge.
To fix this, you'll need to limit the time factor to a maximum amount for each frame. By adding the next code to you game loop, your game will never move ahead more than 0.1 second in (game) time. For a game that would normally run at 60fps, this would still mean you've squeezed 6 frames into 1. You can play with this number to make it fit your game.
// Move forward in time with a maximum amount
secondsPassed = Math.min(secondsPassed, 0.1);
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.
let 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.
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.
What's next?
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. Feel free to ask any questions in the comments. You can download the example code here.
Spicy Yoghurt is here to help you create your own games and inspire you. Building the website takes a lot of spare time and coffee.
We supply the spare time and you can support us with a cup of coffee!
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