Game loops - a JavaScript guide

We're going to learn about a concept called the game loop or animation loop and how we can use it to make things move in JavaScript, with a focus on 2D graphics in the <canvas> element.

Before reading this guide, try my learn JavaScript tutorial or my guide to events and the event loop.

The first few examples assumes this minimal HTML, but you can also press on any example to see the full code:

A simple loop

Our most simple loop is just to run setInterval(func, ms), where ms is the number of milliseconds between each time we run func(). A better loop uses requestAnimationFrame(func) as it syncs with when the browser is ready to draw, so you should use that instead.

We call gameLoop() in 2 places, once to get the first loop running, then again at the end of each loop, via requestAnimationFrame(). We use a variable x which we increment on each loop, and must remember to include px when setting the style. We set the left position of the div with btn1.style.left, but to use that the div must first be positioned with CSS position: relative; too.

Extend: Make the button moves diagonally to the bottom-right of the screen, with a different horizontal and vertical speed.

Using time since last loop

There's no guarantee how much time has passed since the last loop, so we really shouldn't just be incrementing a variable on each loop. Our game loop takes a parameter currentTime which we can compare to the previous time the loop ran, and make calculations based on that difference in time, or delta time dt.

The timeDiff since the previous loop is in milliseconds, so we divide speed by 1,000 to get pixels per second.

Extend: Add some acceleration by increasing the speed over time.

Drawing to a <canvas>

You can manipulate the position of HTML elements in your game loop as per in the last 2 examples, but if you have lots of moving objects you'll probably want to use a <canvas> instead.

Our minimal HTML has now been changed slightly to this. Note that it includes a line of JavaScript which will be explained shortly:

Here's similar code to the last example, but this time for drawing to a <canvas> instead of moving an element:

Before we can draw to a canvas we need to get its drawing context with let ctx = canvas.getContext('2d'). All actions we take on the canvas are methods on this context. Our ctx.clearRect(0, 0, canvas.width, canvas.height) line is very important - try removing it and you'll see it just looks like the box is getting wider. That's because it's being redrawn on each frame but the old drawings are still showing too.

Extend: Add a second rectangle to the canvas, that moves down instead of right.

Keeping track of state

In general, here's what we want our code to look like. The comments starting with ... indicate code which is missing but explains what kind of code needs to go there:

Ideally, all our state lives in one {} object. This enables us to see it all in one place, and to do things like save the state to localStorage by converting it to JSON first. Let's see what it looks like with some state added:

Extend: Give the baddies a direction and have them move at their speed in that direction.

Splitting into functions

At this stage, it makes sense to split our code into functions with sensible names.

Now we only really need to touch code in our 3 functions: initState(), updateState() and drawState().

Extend: Try modifying the 3 functions a bit to make the game work slightly differently.

Responding to user events

Our game isn't much good without the user interacting with it somehow. Have a look at the events guide for how to respond to events such as from the mouse or keyboard.

Extra: Rotating objects

I get asked a lot about the best way to rotate objects, so thought I'd add that in here.

When drawing to the canvas, the coordinte system's origin is in the top-left corner, with y increasing as you go down instead of up. If you have an object you want to rotate, the best strategy is to change the coordinate system to have its origin at the centre of your object instead, then rotate around that new origin. Before we do these translations and rotations we must call ctx.save() then ctx.restore() when we're done to go back to our regular coordinate system as it was before the translation and rotation.

Extra: using images

Drawing images to a <canvas> is slightly harder that using a regular <img> element.

This assumes you have an image called mario.png in the same folder as the web page. First we need to make a new image in JavaScript, and set its src attribute to be the image we have on disk.

Then we replace our call to ctx.fillRect() with ctx.drawImage() instead.

What next?

Make sure you've tried out a lot of these examples, and extended them with your own code. If you're feeling confident, you may then be ready to learn about rigid-body collisions of particles.