How To Get Started With Canvas Animations In JavaScript
Tags: javascript, gamedev, webdev
Published: 2021-05-26 06:06:03

I love playing games. And I love coding too. So, one day, I got thinking, why not use those coding skills to make a game? But it sounds hard. How would one even get started?

With baby steps. 👣

In this article, we learn to draw and animate objects using HTML5 Canvas and JavaScript before we optimize for performance.

> “Animation is not the art of drawings that move but the art of movements that are drawn.” — Norman McLaren

Banner photo by [Justin Lim](https://unsplash.com/@justinlim) on Unsplash 

## Introduction

Apple introduced canvas in 2004 to power applications and the Safari browser. A few years later it was standardized by the [WHATWG](https://en.wikipedia.org/wiki/WHATWG). It comes with finer-grained control over rendering but with the cost of having to manage every detail manually. In other words, it can handle many objects, but we need to code everything in detail.

The canvas has a **2D drawing context** used for drawing shapes, text, images, and other objects. First, we choose the color and brush, and then we paint. We can change the brush and color before every new drawing, or we can continue with what we have.

Canvas uses **immediate rendering**: When we draw, it immediately renders on the screen. But, it is a fire-and-forget system. After we paint something, the canvas forgets about the object and only knows it as pixels. So there is no object that we can move. Instead, we have to draw it again.

Doing animations on Canvas is like making a stop-motion movie. In every frame need to move the objects a little bit to animate them.

## Canvas element

The HTML `<canvas>` element provides a blank container on which we can draw graphics. We can draw shapes and lines on it via the [Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API), which allows for drawing graphics via JavaScript.

A canvas is a rectangular area on an HTML page that by default has no border or content. The default size of the canvas is 300 pixels × 150 pixels (width × height). However, custom sizes can be defined using the HTML `height` and `width` property:

```html
<canvas id="canvas" width="600" height="300"></canvas>
```

Specify the `id` attribute to be able to refer to it from a script. To add a border, use the `style` attribute or use CSS with the `class` attribute:

```html
<canvas id="canvas" width="600" height="300" style="border: 2px solid"></canvas>
<button onclick="animate()">Play</button>
```

Now that we added the border we see the size of our empty canvas on the screen.
We also have a button with an `onclick` event to run our `animate()` function when we click it.

![empty canvas](https://cdn.hashnode.com/res/hashnode/image/upload/v1621357533321/PF3r7aTph.png)

We can place our JavaScript code in `<script>` elements that we place into the document `<body>` after the `<canvas>` element:

```html
<script type="text/javascript" src="canvas.js"></script>
```

We get a reference to the HTML `<canvas>` element in the DOM (Document Object Model) with the `getElementById()` method:

```js
const canvas = document.getElementById('canvas');
```

Now we have the canvas element available but cannot draw directly on it. Instead, the canvas has rendering contexts that we can use.

## Canvas context

The canvas has a **2D drawing context** used for drawing shapes, text, images, and other objects. First, we choose the color and brush, and then we paint. We can change the brush and color before every new drawing, or we can continue with what we have.

The `HTMLCanvasElement.getContext()` method returns a drawing context, where we render the graphics. By supplying `'2d'` as the argument we get the canvas 2D rendering context:

```js
const ctx = canvas.getContext('2d');
```

> There are other available contexts, like `webgl` for a three-dimensional rendering context, that is outside the scope of this article.

The `CanvasRenderingContext2D` has a variety of methods for drawing lines and shapes on the canvas. To set the color of the line we use `strokeStyle` and to set the thickness we use `lineWidth`:

```js
ctx.strokeStyle = 'black';
ctx.lineWidth = 5;
```

Now, we are ready to draw our first line on the canvas. But, before we do that we need to understand how we tell the canvas where to draw. The HTML canvas is a two-dimensional grid. The upper-left corner of the canvas has the coordinates (0, 0).

```
   X →
Y [(0,0), (1,0), (2,0), (3,0), (4,0), (5,0)]
↓ [(0,1), (1,1), (2,1), (3,1), (4,1), (5,1)]
  [(0,2), (1,2), (2,2), (3,2), (4,2), (5,2)]
```

So, when we say we want to `moveTo(4, 1)` on the canvas it means we start at the upper-left corner (0,0) and move four columns to the right and one row down.

## Drawing 🔵

Once we have a canvas context, we can draw on it using the canvas context API. The method `lineTo()` adds a straight line to the current sub-path by connecting its last point to the specified (x, y) coordinates. Like other methods that modify the current path, this method does not directly render anything. To draw the path onto a canvas, you can use the `fill()` or `stroke()` methods.

```js
ctx.beginPath();      // Start a new path
ctx.moveTo(100, 50);  // Move the pen to x=100, y=50.
ctx.lineTo(300, 150); // Draw a line to x=300, y=150.
ctx.stroke();         // Render the path
```

![canvas with line](https://cdn.hashnode.com/res/hashnode/image/upload/v1621283728432/OFb_xUlgq.png)

We can use `fillRect()` to draw a filled rectangle. Setting the `fillStyle` determines the color used when filling drawn shapes:

```js
ctx.fillStyle = 'blue';
ctx.fillRect(100, 100, 30, 30); // (x, y, width, height);
```

This draws a filled blue rectangle:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1621362040705/O88HvsiJZ.png)

## Animation 🎥

Now, let's see if we can get our block to move on the canvas. We start by setting the `size` of the square to 30. Then, we can move the `x` value to the right with steps of `size` and draw the object over and over again. We move the block to the right until it reaches the canvas end:

```js
const size = 30;
ctx.fillStyle = 'blue';

for (let x = 0; x < canvas.width; x += size) {
  ctx.fillRect(x, 50, size, size);
}
```

![animate blue square](https://cdn.hashnode.com/res/hashnode/image/upload/v1621369192655/D4cTMqANv.png)

OK, we were able to draw the square as we wanted. But we have two issues:

1. We are not cleaning up after ourselves.
2. It's too fast to see the animation.

We need to clear away the old block. What we can do is erase the pixels in a rectangular area with `clearRect()`. By using the width and height of the canvas, we can clean it between paints.

```js
for (let x = 0; x < canvas.width; x += size) {
  ctx.clearRect(0, 0, canvas.width, canvas.height); // Clean up
  ctx.fillRect(x, 50, size, size);
}
```

![clear rect](https://cdn.hashnode.com/res/hashnode/image/upload/v1621369427867/HnD87zfY3.png)

Great! We fixed the first problem. Now let's try to slow down the painting so we can see the animation.

You might be familiar with [`setInterval(function, delay)`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval). It starts repeatedly executing the specified `function` every `delay` milliseconds. I set the interval to 200 ms, which means the code runs five times a second.

```js
let x = 0;
const id = setInterval(() => {
  ctx.clearRect(0, 0, canvas.width, canvas.height);      
  ctx.fillRect(x, 50, size, size);
  x += size;

  if (x >= canvas.width) {
    clearInterval(id);
  }
}, 200);    
```

To stop a timer created by `setInterval()`, we need to call `clearInterval()` and give it the identifier for the interval to cancel. The id to use is the one that is returned by `setInterval()`, and this is why we need to store it.

We can now see that if we press the button, we get a square that moves from left to right. But, if we press the play button several times, we can see that there is a problem animating multiple squares at the same time.

![animate many blocks](https://cdn.hashnode.com/res/hashnode/image/upload/v1621371364387/VMLssOIRg.gif)

Every square gets its interval that clears the board and paints the square.
It's all over the place! Let's see how we can fix this.

## Multiple objects

To be able to run the animations for several blocks, we need to rethink the logic. As of now, each block gets its animation method with `setInterval()`. Instead, we should manage the moving objects before sending them to be drawn, all at once.

We can add a variable `started` to only start `setInterval()` on the first button click. Every time we press the play button, we add a new value 0 to a `squares` array. This is enough for this simple animation but for something more complex we could create a `Square` object with the coordinates and eventual other properties like color.

```js
let squares = [];
let started = false;

function play() {
  // Add 0 as x value for object to start from the left.
  squares.push(0);

  if (!started) {
      started = true;
      setInterval(() => {
        tick();
      }, 200)
  }
}

function tick() {
  // Clear canvas
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // Paint objects
  squares.forEach(x => ctx.fillRect(x, 50, size, size));

  squares = squares.map(x => x += size) // move x to right
      .filter(x => x < canvas.width);  // remove when at end
}
```
The `tick()` function clears the screen and paints all the objects in the array every 200ms. And by having only one interval, we avoid the flicker we had before. And now we get better animations:

![multiple inteval working.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1621808272719/IHOF8JnDW.gif)

What we did here is the first step of making a game loop. This loop is the heart of every game. It's a controlled infinite loop that keeps your game running ;  it's the place where all your little pieces get updated and drawn on the screen.

## 🚶Optimize animations 🏃

Another option for animating is to use `requestAnimationFrame()`. It tells the browser that you wish to perform an animation and requests the browser to call a function to update an animation before the next repaint. In other words, we tell the browser: "Next time you paint on the screen, also run this function because I want to paint something too."

The way to animate with `requestAnimationFrame()` is to create a function that paints a frame and then schedules itself to invoke again. With this, we get an asynchronous loop that executes when we draw on the canvas. We invoke the animate method over and over again until we decide to stop. So, now we instead call the `animate()` function:

```js
function play() {
  // Add 0 as x value for object to start from the left.
  squares.push(0);

  if (!started) {
      animate();
  }
}

function animate() {
  tick();
  requestAnimationFrame(animate);  
}
```

If we try this out we notice that we can see the animation, which was not the case with `setInterval()`, even though it's super fast. The number of callbacks is usually 60 times per second.

The `requestAnimationFrame()` method returns an `id` that we use for canceling the scheduled animation frame. To cancel a scheduled animation frame, you can use the `cancelAnimationFrame(id)` method.

To slow down the animation we need a timer to check the `elapsed` time since the last time we called the `tick()` function. To help us, the callback function is passed an argument, a `DOMHighResTimeStamp`, indicating the point in time when `requestAnimationFrame()` starts to execute callback functions.

```js
let start = 0;

function animate(timestamp) {    
  const elapsed  = timestamp - start;
  if (elapsed > 200) {
    start = timestamp;
    tick();
  }
  requestAnimationFrame(animate);  
}
```

With this, we have the same functionality as we had earlier with `setInterval()`.

So, in conclusion, why should we use `requestAnimationFrame()` instead of `setInterval()`?

- It enables browser optimizations.
- It handles the frame rate.
- Animations only run when visible.

## Conclusion

In this article, we created an *HTML5 Canvas* and used its *2D rendering context* and JavaScript to draw on the canvas. We got introduced to some of the methods available in the canvas context and used them to render different shapes.

Finally, we were able to animate multiple objects on the canvas. We learned how to use `setInterval()` to create an animation loop that manages and draws the objects on the screen.
We also learned how to optimize animations with `requestAnimationFrame()`.

With this intro to canvas animations, we have taken our first steps into game development. We are ready to start on a real game next:

- [A course on Creating Tetris with Educative](https://www.educative.io/courses/game-development-js-tetris)

- [A blog on Creating Tetris](https://blog.michaelkaren.dev/learning-modern-javascript-with-tetris-92d532bcd057)