Map Custom Mouse Events to Matter.Mouse in Matter.js

This guide provides a straightforward method for mapping custom DOM mouse or pointer events to a Matter.Mouse instance in Matter.js. By intercepting events from an external DOM layer—such as a UI overlay, an SVG viewport, or an offscreen target—and manually updating the internal state of Matter.Mouse, you can drive physics interactions and MouseConstraint behaviors independently of default canvas listeners.

Understanding Matter.Mouse State

By default, Matter.Mouse.create(element) binds native DOM listeners directly to the target canvas. The instance relies on several core properties to synchronize physics interactions:

When mapping custom DOM events, you can bypass the default listeners and feed values directly into these properties.

Step 1: Create an Unbound or Detached Mouse

Instantiate the Mouse object using an element that does not conflict with your main canvas, or clear the default listeners from the canvas by detaching them.

const { Engine, Render, Runner, Bodies, Composite, Mouse, MouseConstraint } = Matter;

const engine = Engine.create();
const render = Render.create({
  element: document.body,
  engine: engine,
  canvas: document.getElementById('physics-canvas')
});

// Create the mouse bound to the physics canvas initially
const mouse = Mouse.create(render.canvas);

// Remove default event listeners to prevent duplicate processing
render.canvas.removeEventListener('mousedown', mouse.mousedown);
render.canvas.removeEventListener('mousemove', mouse.mousemove);
render.canvas.removeEventListener('mouseup', mouse.mouseup);
render.canvas.removeEventListener('mousewheel', mouse.mousewheel);

// Attach the MouseConstraint to the engine using this mouse instance
const mouseConstraint = MouseConstraint.create(engine, {
  mouse: mouse,
  constraint: {
    stiffness: 0.2,
    render: { visible: true }
  }
});

Composite.add(engine.world, mouseConstraint);

Step 2: Forward Custom DOM Events

Identify your custom DOM element (for example, an overlay div or SVG element) and bind your custom event handlers to update the Matter.Mouse instance manually.

const customOverlay = document.getElementById('custom-overlay');

function getRelativeCoordinates(event, targetElement) {
  const rect = targetElement.getBoundingClientRect();
  return {
    x: event.clientX - rect.left,
    y: event.clientY - rect.top
  };
}

// Handle pointer movement
customOverlay.addEventListener('pointermove', (event) => {
  const coords = getRelativeCoordinates(event, render.canvas);

  mouse.position.x = coords.x;
  mouse.position.y = coords.y;
  mouse.sourceEvents.mousemove = event;
});

// Handle pointer down
customOverlay.addEventListener('pointerdown', (event) => {
  const coords = getRelativeCoordinates(event, render.canvas);

  mouse.position.x = coords.x;
  mouse.position.y = coords.y;
  mouse.button = event.button;
  mouse.sourceEvents.mousedown = event;
});

// Handle pointer up
customOverlay.addEventListener('pointerup', (event) => {
  mouse.button = -1;
  mouse.sourceEvents.mouseup = event;
});

Step 3: Handle Pixel Ratio and Canvas Scaling

If your renderer scales the canvas via CSS or handles high-DPI displays with a pixelRatio, adjust the coordinates before assigning them to mouse.position:

function getScaledCoordinates(event, canvas) {
  const rect = canvas.getBoundingClientRect();
  const scaleX = canvas.width / rect.width;
  const scaleY = canvas.height / rect.height;

  return {
    x: (event.clientX - rect.left) * scaleX,
    y: (event.clientY - rect.top) * scaleY
  };
}

Assigning the calculated scaleX and scaleY values ensures the world coordinates accurately align with the custom element's viewport positions.