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>
main.js
window.onload = () => {
// Add your code here
console.log('Hello, Canvas Animation!');
}
styles.css
/* Add your styles here */
When you're done you should have a folder structure similar to this.
/canvas-demo
index.html
main.js
styles.css
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>
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>
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;
}
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');
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);
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
);
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();
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();
}
}
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);
};
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;
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';
}
}
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;
}
...
}
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);
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);
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);
};
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;
}
}
...
}
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...
Top comments (0)