Canvas Screen Shake Without Moving Matter.js Bodies
This article explains how to create a high-impact screen shake effect in Matter.js by transforming the HTML5 Canvas context instead of altering physical body coordinates. By applying transformations directly to the 2D rendering context, you preserve the stability and accuracy of the physics simulation while dynamically translating the visual viewport.
The Problem with Altering Physics Bodies
Modifying Matter.Body positions or applying physical
impulses to simulate screen shake disrupts the simulation. It causes
unwanted collisions, destabilizes stacked structures, and introduces
unpredictable forces. The correct approach is decoupling the physics
state from the visual presentation by shaking the canvas "camera"
instead of the world objects.
The Screen Shake Algorithm
A standard screen shake relies on two main variables: intensity (the maximum pixel displacement) and decay (how quickly the effect fades). On each frame, you calculate a random displacement on the X and Y axes within the bounds of the current intensity, then reduce the intensity over time until it reaches zero.
let shakeIntensity = 0;
const shakeDecay = 0.9;
function triggerShake(magnitude) {
shakeIntensity = magnitude;
}
function updateShake() {
if (shakeIntensity > 0.1) {
const offsetX = (Math.random() * 2 - 1) * shakeIntensity;
const offsetY = (Math.random() * 2 - 1) * shakeIntensity;
shakeIntensity *= shakeDecay;
return { x: offsetX, y: offsetY };
}
shakeIntensity = 0;
return { x: 0, y: 0 };
}Hooking into the Matter.js Render Loop
If you are using the built-in Matter.Render module, you
can inject canvas transformations using the engine's render events:
beforeRender and afterRender.
beforeRender: Save the current canvas state and translate the context by the computed shake offset before Matter.js draws the bodies.afterRender: Restore the canvas context to its original state so that subsequent frames do not compound the translation.
const { Engine, Render, Runner, Events } = Matter;
const engine = Engine.create();
const render = Render.create({
element: document.body,
engine: engine,
options: {
width: 800,
height: 600,
wireframes: false
}
});
Render.run(render);
Runner.run(Runner.create(), engine);
// Hook canvas transformations into Matter.js rendering
Events.on(render, 'beforeRender', () => {
const context = render.context;
const offset = updateShake();
context.save();
context.translate(offset.x, offset.y);
});
Events.on(render, 'afterRender', () => {
render.context.restore();
});Implementing with a Custom Render Loop
When managing your own rendering loop using
requestAnimationFrame, wrap your drawing routines between
context.save(), context.translate(), and
context.restore():
function draw() {
const ctx = canvas.getContext('2d');
const offset = updateShake();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(offset.x, offset.y);
// Render Matter.js bodies manually here
const bodies = Matter.Composite.allBodies(engine.world);
bodies.forEach(body => {
// Draw vertices
});
ctx.restore();
requestAnimationFrame(draw);
}Handling Mouse and Screen Interactions
Because screen shake temporarily misaligns the visual position of
bodies with their underlying coordinate space, screen-space inputs (like
mouse clicks or touch coordinates) will not match physics coordinates
during intense shaking. If precise interaction is needed during an
impact, subtract the current frame's shake offset (offsetX,
offsetY) from the screen coordinates before querying
Matter.js bodies with methods like
Matter.Query.point().