Render Matter.js with Hardware-Accelerated SVG

This guide explains how to decouple Matter.js from its default HTML5 Canvas renderer to drive Scalable Vector Graphics (SVG) directly inside the DOM. By mapping physics bodies to SVG nodes and manipulating them via CSS 3D transforms rather than redrawing geometry or updating coordinate attributes, you enable the browser’s compositor to handle rendering on the GPU for smooth, hardware-accelerated performance.

The Rendering Principle

By default, Matter.js uses an HTML5 Canvas via Matter.Render. To use SVG, you run the Matter.js physics engine in a headless state and handle the DOM updates manually.

Modifying SVG geometric attributes like cx, cy, x, or y on every frame causes the browser to recalculate layouts and re-rasterize vector paths on the CPU. To achieve hardware acceleration:

  1. Render SVG elements once at origin (0, 0).
  2. Set their visual center as the transform origin using CSS.
  3. Update positions and rotations via CSS transform: translate3d(...) rotate(...) on each engine tick. This offloads translation and rotation directly to the GPU compositor.

1. Set Up the SVG Canvas and CSS

Create an SVG container in your HTML and configure the CSS to promote SVG elements to their own GPU compositing layers:

<svg id="scene" width="800" height="600" viewBox="0 0 800 600"></svg>
.physics-node {
  will-change: transform;
  transform-box: fill-box;
  transform-origin: center center;
  position: absolute;
}

2. Initialize the Headless Matter.js Engine

Create the Matter.js engine and runner without initializing Matter.Render:

const { Engine, Runner, Bodies, Composite } = Matter;

const engine = Engine.create();
const world = engine.world;
const runner = Runner.create();

Runner.run(runner, engine);

3. Generate Physics Bodies and Corresponding SVG Elements

When creating a body, create a corresponding SVG element. Center the element's base geometry at (0, 0) so that translational transforms position it accurately.

const svgContainer = document.getElementById('scene');
const bodiesMap = new Map();

function createSvgRect(body, width, height) {
  const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
  rect.setAttribute("width", width);
  rect.setAttribute("height", height);
  // Center the geometry around (0, 0)
  rect.setAttribute("x", -width / 2);
  rect.setAttribute("y", -height / 2);
  rect.setAttribute("class", "physics-node");
  rect.setAttribute("fill", "#3498db");

  svgContainer.appendChild(rect);
  bodiesMap.set(body, rect);
  return rect;
}

// Example: Create a falling rectangle
const box = Bodies.rectangle(400, 100, 60, 60);
Composite.add(world, box);
createSvgRect(box, 60, 60);

// Example: Create a static floor
const floor = Bodies.rectangle(400, 580, 800, 40, { isStatic: true });
Composite.add(world, floor);
createSvgRect(floor, 800, 40);

4. Sync the Physics State with the DOM

Hook into Matter.js's afterUpdate event to synchronize the coordinates of each body with its SVG element using CSS transform:

Matter.Events.on(engine, 'afterUpdate', () => {
  bodiesMap.forEach((element, body) => {
    const { x, y } = body.position;
    const angle = body.angle;

    // Use translate3d to force hardware acceleration
    element.style.transform = `translate3d(${x}px, ${y}px, 0) rotate(${angle}rad)`;
  });
});

Complex Geometries and Polygons

For complex shapes generated with Bodies.fromVertices:

  1. Generate an SVG <path> or <polygon> element.
  2. Normalize the SVG points around the calculated center of mass (body.position).
  3. Apply the exact same translate3d and rotate CSS properties on the resulting element during the afterUpdate loop.