How to Use Planck.js with HTML5 Canvas?
Planck.js is a 2D physics engine for JavaScript that allows developers to simulate realistic physics in the browser. While it handles all the complex mathematics behind movement, collisions, and forces, it does not include a built-in renderer. To bring your physics simulation to life, you must pair it with a visual tool like the HTML5 Canvas API. This article provides a step-by-step guide on how to configure a Planck.js world, map its physical coordinates to visual canvas pixels, and create a continuous render loop to display moving bodies.
Setting Up the Physics World
To get started, you need to instantiate a Planck.js world and populate it with rigid bodies. In Planck.js, coordinates are typically measured in meters, kilograms, and seconds (MKS), which keeps the physics calculations stable and realistic.
// Import or access planck
const pl = planck;
// Create a world with downward gravity (e.g., 10 m/s²)
const world = pl.World({
gravity: pl.Vec2(0, -10)
});
// Create a dynamic body (a falling box)
const body = world.createBody({
type: 'dynamic',
position: pl.Vec2(0, 10)
});
// Give the body a box shape (width: 2 meters, height: 2 meters)
body.createFixture(pl.Box(1, 1), {
density: 1.0,
friction: 0.3
});The Coordinate Mapping Problem
HTML5 Canvas uses a coordinate system where the origin \((0,0)\) sits at the top-left corner, and the y-axis increases as you move downward. Conversely, a typical physics environment uses a standard Cartesian coordinate system where the y-axis increases as you move upward.
Because physics engines work best with small numbers (e.g., a human character being 2 meters tall rather than 200 pixels tall), you must establish a scale factor to translate meters to pixels. A common ratio is 30 pixels per meter.
When drawing, you will need to:
- Multiply the physics positions by your scale factor.
- Invert the Y-axis so that physics objects falling “down” move downward on your screen.
- Translate the origin to the center or bottom of your canvas if desired.
Implementing the Render Loop
To see the simulation, you must update the physics world and redraw
the canvas on every frame. This is achieved using
requestAnimationFrame. During each frame, the canvas is
cleared, the physics world advances by a time step, and the current
position and angle of every body are used to draw shapes.
const canvas = document.getElementById('simCanvas');
const ctx = canvas.getContext('2d');
const SCALE = 30; // 30 pixels = 1 meter
function draw() {
// 1. Clear the canvas for the new frame
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 2. Advance the physics world (60 frames per second time step)
world.step(1 / 60);
// 3. Iterate through all bodies and draw them
for (let b = world.getBodyList(); b; b = b.getNext()) {
const pos = b.getPosition();
const angle = b.getAngle();
// Save context state to handle individual object transformations
ctx.save();
// Translate canvas origin to the center of the canvas and scale coordinates
ctx.translate(canvas.width / 2 + pos.x * SCALE, canvas.height / 2 - pos.y * SCALE);
ctx.rotate(-angle); // Invert rotation due to the inverted Y-axis
// Draw the body shape (assuming a box fixture for simplicity)
ctx.fillStyle = '#3498db';
ctx.fillRect(-30, -30, 60, 60); // 2x2 meters becomes 60x60 pixels
ctx.restore();
}
// Loop the animation
requestAnimationFrame(draw);
}
// Start the loop
requestAnimationFrame(draw);Optimizing for Complex Shapes
While the example above uses a hardcoded rectangle, a
production-ready canvas renderer should dynamically inspect the fixtures
of a body. By checking whether a fixture is a Circle or a
Polygon, you can read the local vertices provided by
Planck.js, scale them, and use standard canvas path methods like
ctx.arc() or ctx.lineTo() to draw accurate
shapes regardless of their geometry.