Update Matter.js Mouse Offset on Canvas Scale
When scaling a canvas or its parent container in Matter.js, the mouse
coordinates often become misaligned with the physics bodies in the
world. This article explains how to correctly synchronize the mouse
position by updating the Mouse.setScale and
Mouse.setOffset methods in Matter.js whenever the canvas
dimensions, CSS transform, or rendering scale change.
The Cause of Mouse Desynchronization
By default, Matter.js attaches a mouse listener directly to the
canvas element using Mouse.create(render.canvas). The mouse
module maps the pointer's screen coordinates directly to the canvas's
internal physics dimensions.
When you scale the canvas—whether via CSS transforms
(transform: scale(...)), dynamic responsive resizing, or
Matter's camera view methods (Render.lookAt)—the standard
mapping breaks. The physics engine continues evaluating mouse
coordinates based on the original pixel dimensions, causing dragged
bodies to lag, jump, or completely miss the cursor.
The
Solution: Using Mouse.setScale and
Mouse.setOffset
Matter.js provides built-in methods on the Matter.Mouse
module to correct scale factors and positional offsets:
Matter.Mouse.setScale(mouse, scaleVector): Adjusts the scale multiplier for mouse movements along the X and Y axes.Matter.Mouse.setOffset(mouse, offsetVector): Shifts the mouse coordinates to account for translation, margins, or camera panning.
Implementation Example
To keep the mouse accurately aligned, compute the ratio between the
canvas's internal resolution (canvas.width and
canvas.height) and its rendered display size using
getBoundingClientRect().
// Initialize Matter.js modules
const { Engine, Render, Runner, Bodies, Composite, Mouse, MouseConstraint } = Matter;
const engine = Engine.create();
const render = Render.create({
element: document.body,
engine: engine,
options: {
width: 800,
height: 600,
wireframes: false
}
});
// Setup mouse control
const mouse = Mouse.create(render.canvas);
const mouseConstraint = MouseConstraint.create(engine, {
mouse: mouse,
constraint: {
stiffness: 0.2,
render: { visible: false }
}
});
Composite.add(engine.world, mouseConstraint);
render.mouse = mouse;
// Function to update mouse scale and offset
function updateMouseScale() {
const canvas = render.canvas;
const rect = canvas.getBoundingClientRect();
// Calculate scale factor between canvas internal size and display size
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
// Apply scale to the mouse
Mouse.setScale(mouse, { x: scaleX, y: scaleY });
// Apply offset if the canvas has margins or translations
Mouse.setOffset(mouse, {
x: -rect.left * scaleX,
y: -rect.top * scaleY
});
}
// Recalculate whenever the window resizes or zoom changes
window.addEventListener('resize', updateMouseScale);
window.addEventListener('scroll', updateMouseScale);
// Initial call to set correct coordinates
updateMouseScale();Handling Camera View
Changes (Render.lookAt)
If the scaling occurs due to camera zooming using
Render.lookAt, the canvas display size remains unchanged,
but the engine's visible bounds adjust. In this scenario, compute the
scale using the renderer's bounds:
function updateCameraMouse(render, mouse) {
const bounds = render.bounds;
const canvas = render.canvas;
const scaleX = (bounds.max.x - bounds.min.x) / canvas.width;
const scaleY = (bounds.max.y - bounds.min.y) / canvas.height;
Mouse.setScale(mouse, { x: scaleX, y: scaleY });
Mouse.setOffset(mouse, bounds.min);
}Whenever you modify render.bounds or call
Render.lookAt(), immediately invoke this update function to
keep pointer interactions seamlessly aligned with physics objects.