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:

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.