Fix Matter.js MouseConstraint with CSS Canvas Scaling
When styling a Matter.js canvas with responsive CSS rules such as
width: 100% or dynamic flex layouts, mouse and touch
interactions often drift out of alignment with the underlying physics
bodies. This occurs because the physics engine calculates pointer events
based on internal canvas pixel dimensions rather than the element's
rendered CSS size. This guide demonstrates how to correct this
misalignment by calculating the coordinate ratio and updating the
Matter.js Mouse instance directly.
The Cause of Misaligned Coordinates
Matter.js binds a Mouse object to the canvas element to
handle interactions via MouseConstraint. By default, it
assumes a 1:1 mapping between screen pixels and canvas pixel
coordinates. If the canvas element has internal rendering dimensions of
800x600 (canvas.width and
canvas.height), but CSS scales it to 400x300
or stretches it across a viewport, the pointer's client coordinates will
register at half the expected physics distance, preventing bodies from
being dragged correctly.
The Solution: Using
Mouse.setScale
To resolve the discrepancy, you must compute the ratio between the
canvas's internal dimensions and its actual rendered bounding box, then
supply these values to Matter.Mouse.setScale.
Here is the implementation:
const { Engine, Render, Runner, Bodies, Composite, Mouse, MouseConstraint } = Matter;
// 1. Create engine and render
const engine = Engine.create();
const canvas = document.getElementById('world');
const render = Render.create({
canvas: canvas,
engine: engine,
options: {
width: 800,
height: 600,
wireframes: false
}
});
Render.run(render);
Runner.run(Runner.create(), engine);
// 2. Add dynamic bodies to the world
const box = Bodies.rectangle(400, 200, 80, 80);
const ground = Bodies.rectangle(400, 590, 810, 60, { isStatic: true });
Composite.add(engine.world, [box, ground]);
// 3. Create mouse and MouseConstraint
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;
// 4. Function to adjust mouse scale based on rendered dimensions
function updateMouseScale() {
const rect = canvas.getBoundingClientRect();
// Prevent division by zero if canvas is hidden
if (rect.width === 0 || rect.height === 0) return;
Mouse.setScale(mouse, {
x: canvas.width / rect.width,
y: canvas.height / rect.height
});
Mouse.setOffset(mouse, {
x: 0,
y: 0
});
}
// Initial adjustment
updateMouseScale();
// Re-adjust whenever the window or container resizes
window.addEventListener('resize', updateMouseScale);Addressing Margins and Offsets
In cases where the canvas is embedded inside transformed containers, scrollable elements, or elements with heavy padding, calculate the offset dynamically as well:
function updateMouseScaleAndOffset() {
const rect = canvas.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) return;
// Scale factor: internal dimensions / visible CSS dimensions
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
Mouse.setScale(mouse, { x: scaleX, y: scaleY });
// Optional: Set offset if external bounds introduce coordinate shift
Mouse.setOffset(mouse, {
x: -rect.left * (scaleX - 1),
y: -rect.top * (scaleY - 1)
});
}Responsive Canvases with Native Pixel Resizing
If your goal is to change the actual physics boundaries along with the window size (rather than scaling the existing physics world), resize the internal canvas dimensions directly and update the bounds:
window.addEventListener('resize', () => {
render.canvas.width = window.innerWidth;
render.canvas.height = window.innerHeight;
render.options.width = window.innerWidth;
render.options.height = window.innerHeight;
// Reset scale back to 1:1 since the internal resolution matches the screen size
Mouse.setScale(mouse, { x: 1, y: 1 });
Mouse.setOffset(mouse, { x: 0, y: 0 });
});Using Mouse.setScale ensures that user input accurately
matches rendered geometry, regardless of whether CSS transforms,
percentage widths, or device pixel ratios are applied to the canvas.