In this example, we learn how to animate a bouncing ball on the canvas. We need to use the canvas loop(dt) callback function which can compute the new state and render each frame.
Our Ball and Ground classes both implement Drawable by implementing the draw(ctx) function which is used to render the objects onto the canvas.
For our Ball class, we also want to create a custom step(dt) function to compute the motion of the ball at each interval. Note that dt is in milliseconds, so if we want to convert that into seconds, we need to divide by 1,000.
$$\text{s} = \frac{\text{ms}}{1000}$$
We have also provided a keydown(key) event listener, that when space is pressed, the ball is dropped again, resetting the animation.
Follow these steps to create a new webpack project and install the graphico dependency to run this example.
# Create and open project folder
mkdir Animation_demo
cd Animation_demo
# Initialize project and install dependencies
npm init -y
npm pkg set type="module"
npm i graphico@1.1.0 webpack-cli
# Create source files
touch index.html
mkdir src
touch src/index.js
# Open new files
open index.html
open src/index.js
Copy and paste the following source code blocks into the newly created files.
<html>
<head>
<script type="text/javascript" src="dist/main.js" defer></script>
</head>
<body>
<p>Press <kbd>space</kbd> to restart animation.</p>
</body>
</html>
import { Canvas } from 'graphico';
const px_per_m = 100; // Pixels per meter
const G = 9.81; // Gravitation constant [m/s^2]
class Ball {
#color;
#x;
#r;
#y;
#vy;
#groundY;
constructor(color = '', radius = 0, x = 0, dropHeight = 0, groundY = 0) {
this.#color = color;
this.#x = x;
this.#r = radius;
this.#y = groundY - dropHeight;
this.#vy = 0;
this.#groundY = groundY;
}
reset(dropHeight = 0) {
this.#y = this.#groundY - dropHeight;
this.#vy = 0;
}
step(dt) {
const dt_s = dt / 1000; // convert ms to s
this.#vy += px_per_m * G * dt_s; // [px/m] * [m/s^2] * [s] = [px/s]
this.#y += this.#vy * dt_s; // [px/s] * [s] = [px]
// Check if the ball should bounce off the ground
if (this.#y + this.#r >= this.#groundY) {
this.#y = this.#groundY - this.#r; // make sure ball doesn't go through the ground
this.#vy *= -0.9; // some "energy" is lost
}
}
draw(ctx) {
ctx.fillStyle = this.#color;
ctx.beginPath();
ctx.arc(this.#x, this.#y, this.#r, 0, 2 * Math.PI);
ctx.fill();
}
}
class Ground {
#y;
constructor(y = 0) {
this.#y = y;
}
draw(ctx) {
ctx.fillStyle = 'sandybrown';
ctx.fillRect(0, this.#y, canvas.width, canvas.height);
}
}
// Create the animation objects
const ball = new Ball('red', 10, 100, 100, 150);
const ground = new Ground(150);
// Create the canvas
const canvas = new Canvas({
border: 'black',
borderBlur: 'gray',
background: 'skyblue',
width: 200,
height: 200,
loop(dt) {
ball.step(dt); // Compute the motion for the ball
canvas.clear(); // Clear the canvas before rendering any objects
canvas.draw(ground); // Draw the ground (calls the ground.draw() function)
canvas.draw(ball); // Draw the ball
},
keydown(key) {
// Press space to drop the ball
if (key === ' ') {
ball.reset(100);
}
},
});
In the base project directory Animation_demo/, run the following command. If changes are made in the source files, the demo will automatically be recompiled. Press CTRL+C to stop running.
npx webpack --mode development --watch
Open the index.html file in any web browser of your choice! You should see a page similar to the one below.