Tutorial

Animations with the Canvas API - Part 2: Basic Collisions

Published on October 3, 2019
author

Joshua Hall

Animations with the Canvas API - Part 2: Basic Collisions

In Part 1 of this series we went over the basics of rendering reusable objects to the canvas, using our GUI for more intuitive controls, and creating the illusion of basic movement with our animation loop. In this part we’ll get comfortable with creating collision effects with a simple ball that changes colors as it hits the borders of our canvas.

Boilerplate

We can just use our project from Part 1 as the starting point for most of our animations.

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <meta http-equiv="X-UA-Compatible" content="ie=edge"/>
    <title>HTML Canvas</title>
  </head>
  <body>

    <canvas></canvas>

  </body>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.6/dat.gui.min.js"></script>
  <script src="./canvas.js"></script>
</html>
canvas.js
// Get canvas element
const canvas = document.querySelector('canvas');
const c = canvas.getContext('2d');

// Make canvas fullscreen
canvas.width = innerWidth;
canvas.height = innerHeight;
addEventListener('resize', () => {
  canvas.width = innerWidth;
  canvas.height = innerHeight;
});

// Control Panel
const gui = new dat.GUI();

const controls = {
  dx: 0,
  dy: 0,
};

gui.add(controls, 'dx', 0, 10);
gui.add(controls, 'dy', 0, 10);

// New Object
class Ball {
  constructor(x, y, radius, color) {
    this.x = x;
    this.y = y;
    this.radius = radius;
    this.color = color;
  }
}

Ball.prototype.draw = function () {
  c.beginPath();
  c.fillStyle = this.color;
  c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
  c.fill();
  c.closePath();
};

Ball.prototype.update = function () {
  this.x += controls.dx;
  this.y += -controls.dy;
  this.draw();
};

const ball = new Ball(innerWidth / 2, innerHeight / 2, 50, 'red');

// Render new instances
const init = () => ball.draw();

// Handle changes
const animate = () => {
  requestAnimationFrame(animate);

  c.clearRect(0, 0, canvas.width, canvas.height);

  ball.update();
};

init();
animate();

Bouncer

You can preview the our end result here.

To change our behavior on a collision, we just need to add a condition to our update method that will change the ball’s behavior whenever it hits a border, in this case, reversing its direction. Keep in mind that the browser is looking at the center of our object for its position, so we always want to include the radius into the calculation.

canvas.js
Ball.prototype.update = function() {
  if (this.y + this.radius > canvas.height || this.y - this.radius < 0) {
    controls.dy = -controls.dy;
  }
  this.y -= controls.dy;

  if (this.x + this.radius > canvas.width || this.x - this.radius < 0) {
    controls.dx = -controls.dx;
  }
  this.x += controls.dx;

  this.draw();
};

Colors

Now let’s add some more interesting behavior when we hit something, like changing the ball’s color. First we need an array of colors to choose from and a function to randomly select one for us. Whenever we hit a border we can re-assign our color value to a new random one.

I recommend checking out Kuler to make your own colors palettes.

// Returns a color between 0 and the length of our color array
const randomColor = colors => colors[Math.floor(Math.random() * colors.length)];

const colors = [
  '#e53935',
  '#d81b60',
  '#8e24aa',
  '#5e35b1',
  '#3949ab',
  '#1e88e5',
  '#039be5',
  '#00acc1',
  '#00897b',
  '#43a047',
  '#ffeb3b',
  '#ef6c00'
];

// Re-assign color on contact
Ball.prototype.update = function () {
  if (this.y + this.radius > canvas.height || this.y - this.radius < 0) {
    this.color = randomColor(colors);
    controls.dy = -controls.dy;
  };
  this.y -= controls.dy;

  if (this.x + this.radius > canvas.width || this.x - this.radius < 0) {
    this.color = randomColor(colors);
    controls.dx = -controls.dx;
  };
  this.x += controls.dx;

  this.draw();
};

// Make it start with a random color
const newBall = new Ball(innerWidth / 2, innerHeight / 2, 50, randomColor(colors));

Tail

Now that we have the basic functionality in place we can make it a bit more visually interesting by adding a colored tail that trails behind it. We can do this by removing clearRect and filling our whole canvas with a dark RGBA value. This creates a ‘residue’ effect from anything that moves through it and we can control the residue’s intensity with the background’s opacity.

const animate = () => {
  requestAnimationFrame(animate);

  c.fillStyle = `rgba(33, 33, 33, ${-controls.tail / 10})`; // Lower opacity creates a longer tail
  c.fillRect(0, 0, canvas.width, canvas.height);

  newBall.update();
};

// We also need to update our controls with some default values
const controls = {
  dx: 5,
  dy: 5,
  tail: -5
};

gui.add(controls, 'dx', 0, 10);
gui.add(controls, 'dy', 0, 10);
gui.add(controls, 'tail', -10, 0);

Conclusion

Just like that, we now have a very basic collision system with some special effects. In the upcoming Part 3 of this series we’ll be using the concepts covered here to create this dynamic rain animation.

If you had any problems following along, a working example is available on Codepen. Feel free to fork it and share what you made.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar
Joshua Hall

author

While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Featured on Community

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more