URL: https://www.progressiverobot.com/js-canvas-animations-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

canvas illustration for: Boilerplate

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

				
					
[label 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>

				
			
				
					
[label 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 codepen.io.

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.

				
					
[label 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.