DEV Community

jimjohnson-ui
jimjohnson-ui

Posted on

Javascript Animation with Canvas

Bouncing Around the Screen

In this tutorial you'll learn how to create a basic procedural animation with JavaScript and Canvas: the classic bouncing ball.

This tutorial assumes that you understand modern JavaScript, including things like classes and anonymous functions, but does not assume any knowledge of the Canvas API. If you're not up to speed on the latest JS, don't worry, try to follow along and leave a comment if you need help.

Setting Up

To start create an HTML file and link it to a single CSS and JavaScript file.

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Bouncing Around</title>
    <link rel="stylesheet" href="styles.css">
  </head>
  <body>
    <script src="main.js"></script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

main.js

window.onload = () => {
  // Add your code here
  console.log('Hello, Canvas Animation!');
}
Enter fullscreen mode Exit fullscreen mode

styles.css

/* Add your styles here */
Enter fullscreen mode Exit fullscreen mode

When you're done you should have a folder structure similar to this.

/canvas-demo
  index.html
  main.js  
  styles.css  
Enter fullscreen mode Exit fullscreen mode

We'll be filling out this template in the rest of the tutorial.

Note
The code examples in this tutorial will need to be served via http in order to work properly. Just opening them in a browser won't do it. If you're using VS-Code to follow along you can use the Live Server extension to serve them, or choose another method if this doesn't work for you.

The Canvas Element

The canvas is essentially a blank surface that we can draw on with JavaScript. All of the drawing in this tutorial will be done on one. It has two main attributes, width and height, that we can use to set its size in pixels.

<canvas width="640" height="480"></canvas>
Enter fullscreen mode Exit fullscreen mode

It also accepts inherited attributes like "class" and "id" that we can use for styling.

Place it within the <body /> of an HTML document to use it. In this example we also give it an id of "stage" that we'll use to style it and reference it in JS.

<!DOCTYPE html>
<html lang="en">
  ...
  <body>
    <canvas id="stage" width="640" height="480"></canvas>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

By default the canvas will be invisible because it has a white background and blends into the HTML document. We can change this by setting its background color. Let's give it a black background.

#stage {
  background-color: black;
}
Enter fullscreen mode Exit fullscreen mode

Before moving on to drawing with canvas, here's the complete example up to this point.

Drawing a Ball

To start drawing with canvas we must first get a reference to our canvas#stage and then get its 2D rendering context.

const canvas = document.getElementById('stage');
const context = canvas.getContext('2d');
Enter fullscreen mode Exit fullscreen mode

The context object contains methods for drawing shapes, setting fill and lines styles & much more. We'll be using it to draw our ball.

After getting the context it's usually a good idea to clear the screen before doing any other drawing. We can call the context.clearRec() method to accomplish this. It takes the coordinates of a rectangle as arguments. Pass in the canvas bounding rectangle to clear it.

context.clearRect(0, 0, 640, 480);
Enter fullscreen mode Exit fullscreen mode

With the screen clear, we can use the context's arc method to draw a ball.

context.arc(
  x, y        // Position of the center of arc
  radius,     // Distance from the center of the arc to its outer edge
  startAngle, // Starting position of the arc in radians
  endAngle,   // Ending position
);
Enter fullscreen mode Exit fullscreen mode

Let's start drawing. First set a fillStyle to tell the canvas what color it should be. This can be any valid CSS color for our purposes. Then call canvas.beginPath() to start drawing. Finally draw the ball and fill it in.

context.fillStyle = 'green';
context.beginPath();
context.arc(100, 100, 16, 0, 2 * Math.PI);
context.fill();
Enter fullscreen mode Exit fullscreen mode

We could just paste this code into the onload function in our project and get it to draw. But instead, let's wrap it up into a class. This will come in handy when we get to the animation portion of the tutorial.

class Ball {
  constructor(x, y, radius, color) {
    // Give the ball a position, size and color
    this.x = x ?? 320;
    this.y = y ?? 240;
    this.radius = radius ?? 8;
    this.color = color ?? 'white';
  }

  draw(context) {
    // Draws the ball, using the member vars we defined 
    // in the constructor
    context.fillStyle = this.color;
    context.beginPath();
    context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
    context.fill();
  }
}
Enter fullscreen mode Exit fullscreen mode

Put this code above the window.onload() function.

We can then create an instance of the ball in the window.onload() function and draw it.

window.onload = () => {
  // Get the rendering context
  const canvas = document.getElementById('stage');
  const context = canvas.getContext('2d');

  // Create a green ball at the top left corner of the canvas
  const ball = new Ball(100, 100, 32,  'green');

  // Clear the screen and draw the ball
  context.clearRect(0, 0, 640, 480);
  ball.draw(context);
};
Enter fullscreen mode Exit fullscreen mode

Here's the complete example.

Make it Bounce

The formula for motion that we'll be using is:

new position = current position + direction * speed

where direction is an Euler angle and speed is a scalar. The theory being that if you multiply the direction of an object by its speed then you can calculate the object's position relative to its starting point.

For our purposes this translates into code like this:

const xPosition += xDirection * speed;
const yPosition += yDirection * speed;
Enter fullscreen mode Exit fullscreen mode

To use this in our project we'll need to make some changes to the Ball class. First, add some member variables for direction and speed. While we're at it, let's also refactor the constructor to take a config object as it's argument. Since we're starting to use a large number of variables.

class Ball {
  constructor({ 
    x, 
    y,
    dx, // Direction
    dy,
    speed, // Speed
    radius, 
    color 
  }) {
    this.x = x ?? 320;
    this.y = y ?? 240;
    this.dx = dx ?? 0;
    this.dy = dy ?? 0;
    this.speed = speed ?? 2;
    this.radius = radius ?? 8;
    this.color = color ?? 'white';
  }
}
Enter fullscreen mode Exit fullscreen mode

Next add a move() method to the class that implements the motion formula.

class Ball {
  ...
  move() {
    // Apply the motion formula to this Ball
    this.x += this.dx * this.speed;
    this.y += this.dy * this.speed;
  }
  ...
}
Enter fullscreen mode Exit fullscreen mode

Now that we've updated the Ball class let's look at the window.onload() function. Currently it's pretty static. It just draws a single ball and returns. In order to get things moving we'll need to add a timed loop or something to apply the move calculations at a fixed interval. setInterval seems like a good choice, but there's something even better.

// Takes only one argument, a callback function that 
// draws one frame
requestAnimationFrame(callback);
Enter fullscreen mode Exit fullscreen mode

You use it by calling it once with the callback function and calling it again at the end of that function to create a loop.

// Set up an animation loop
const frame = () => {
  // Draw stuff for one frame
  requestAnimationFrame(frame);
};

requestAnimationFrame(frame);
Enter fullscreen mode Exit fullscreen mode

This will create a 60 FPS frame interval in most browsers.

Let's update our window function to use it.

window.onload = () => {
  const canvas = document.getElementById('stage');
  const context = canvas.getContext('2d');

  // Create an instance of our new Ball class
  const ball = new Ball({ 
    x: 16,
    y: 16, 
    dx: 1,
    dy: 1,
    radius: 32, 
    color: 'green' 
  });

  // Set up the animation
  const frame = () => {
    ball.move();

    context.clearRect(0, 0, 640, 480);
    ball.draw(context);

    requestAnimationFrame(frame);
  };

  // Start the animation
  requestAnimationFrame(frame);
};
Enter fullscreen mode Exit fullscreen mode

What About the Bounce?

To make our ball bounce on the walls we can update the Ball.move() function to check for when the ball moves off the canvas and reverse its direction.

class Ball {
  ...
    move() {
    this.x += this.dx * this.velocity;
    this.y += this.dy * this.velocity;

    // Bounce off the walls
    if (this.x < 0 || this.x > 640) {
      this.dx = -this.dx;
    }
    if (this.y < 0 || this.y > 480) {
      this.dy = -this.dy;
    }
  }
...
}
Enter fullscreen mode Exit fullscreen mode

You can see the finished project below.

Bonus

That's it for this tutorial. I hope you enjoyed it and maybe even learned a thing or two. Tell me what you think in the comments.

And as a parting gift, here's a CodePen with a hundred colorful bouncing balls...

References

Top comments (0)