Using Matter.js with React and Modern Frameworks
Matter.js is a robust 2D physics engine for the web that integrates seamlessly with modern component-based frameworks like React, Vue, and Svelte. This guide explains how to bridge the gap between declarative UI libraries and the imperative nature of Matter.js, details a standard implementation pattern in React, and covers critical performance considerations to keep your physics simulations running smoothly.
The Challenge: Declarative vs. Imperative
Modern front-end frameworks rely on a declarative programming
paradigm, where the UI is a direct reflection of application state
managed through a virtual DOM or reactive bindings. Conversely,
Matter.js operates imperatively: it instantiates an internal simulation
loop, manages its own state via physics bodies, and directly manipulates
the DOM or an HTML5 <canvas> element on every
tick.
To make them work together, you must encapsulate the Matter.js lifecycle inside the component lifecycle, ensuring the physics engine only initializes once the target DOM element exists and tears down cleanly when the component unmounts.
Implementing Matter.js in React
In React, the connection between the virtual DOM and the Matter.js
engine is handled using useRef and
useEffect.
import React, { useEffect, useRef } from 'react';
import Matter from 'matter-js';
const PhysicsScene = () => {
const sceneRef = useRef(null);
const engineRef = useRef(null);
useEffect(() => {
// 1. Module aliases
const { Engine, Render, Runner, Bodies, Composite } = Matter;
// 2. Create engine
const engine = Engine.create();
engineRef.current = engine;
// 3. Create renderer
const render = Render.create({
element: sceneRef.current,
engine: engine,
options: {
width: 800,
height: 600,
wireframes: false,
background: '#f0f0f0'
}
});
Render.run(render);
// 4. Create runner
const runner = Runner.create();
Runner.run(runner, engine);
// 5. Add bodies
const box = Bodies.rectangle(400, 200, 80, 80, { restitution: 0.8 });
const ground = Bodies.rectangle(400, 590, 810, 60, { isStatic: true });
Composite.add(engine.world, [box, ground]);
// 6. Cleanup on unmount
return () => {
Render.stop(render);
Runner.stop(runner);
Composite.clear(engine.world, false);
Engine.clear(engine);
render.canvas.remove();
render.textures = {};
};
}, []);
return <div ref={sceneRef} />;
};
export default PhysicsScene;Best Practices for Modern Frameworks
1. Proper Cleanup and Teardown
Failing to destroy instances when a component unmounts leads to
memory leaks, multiple running loops, and orphaned
<canvas> elements. Always stop the
Runner and Render, clear the
Composite and Engine, and remove the generated
canvas element in your cleanup hook.
2. Avoid Syncing Physics Data with State
Matter.js updates at 60 frames per second. Storing body positions or
velocities in React state (useState) will trigger 60
re-renders per second, quickly degrading application performance. If you
need to read physics data, use mutable references (useRef),
native canvas rendering, or custom event listeners
(Matter.Events.on) that update non-state variables.
3. Decouple Physics from Rendering
While Matter.js includes a built-in canvas renderer
(Matter.Render), it is primarily intended for debugging and
prototyping. In production-grade applications, you can use Matter.js
strictly as a calculation engine. You can read the positions and angles
of bodies on each tick and render them using custom WebGL renderers
(like PixiJS or Three.js) or targeted SVG/HTML updates.
4. Responsiveness and Dynamic Resizing
To make your simulation responsive to container or window size
changes, attach a ResizeObserver to the parent container.
When the bounds change, update the render.bounds, canvas
dimensions, and reposition any static boundary bodies (such as walls and
floors) to maintain layout consistency.