How to Cap the Frame Rate in Matter.js
This guide explains how to effectively limit the frame rate of a
Matter.js 2D physics simulation. By default, Matter.js attempts to match
the screen's refresh rate through the browser's
requestAnimationFrame API, which can cause inconsistent
physics behavior across displays or consume excessive system resources.
You will learn how to bypass the standard runner and implement a custom
loop that locks physics updates to your desired frames per second
(FPS).
Why the Default Runner Cannot Be Capped Directly
The built-in Matter.Runner.run() method automatically
schedules updates using requestAnimationFrame. On a 60Hz
screen, this executes 60 times per second, but on high-refresh-rate
displays (such as 120Hz or 144Hz), it executes far more frequently. To
reliably cap the frame rate, you must avoid Matter.Runner
and invoke Matter.Engine.update() manually inside a
controlled loop.
Implementing a Throttled Game Loop
The most reliable way to cap the frame rate is to track elapsed time
between frames using performance.now() inside a custom
requestAnimationFrame loop.
Here is the complete implementation:
const engine = Matter.Engine.create();
const render = Matter.Render.create({
element: document.body,
engine: engine
});
Matter.Render.run(render);
// Define your target frame rate
const targetFPS = 30;
const frameInterval = 1000 / targetFPS;
let lastTime = performance.now();
function animate(currentTime) {
requestAnimationFrame(animate);
const delta = currentTime - lastTime;
// Only step the physics engine if enough time has passed
if (delta >= frameInterval) {
// Subtract leftover time to prevent timing drift
lastTime = currentTime - (delta % frameInterval);
// Advance the simulation by the fixed timestep
Matter.Engine.update(engine, frameInterval);
}
}
// Start the custom loop
requestAnimationFrame(animate);Best Practices for Physics Stability
- Keep Time Steps Fixed: Always pass a consistent
delta (e.g.,
frameInterval) intoMatter.Engine.update(engine, delta). Passing variable deltas causes physics instability, which can lead to objects tunneling through walls or vibrating. - Avoid
setInterval: WhilesetIntervalcan technically cap execution, it suffers from severe timing drift and runs inefficiently in inactive browser tabs. CombiningrequestAnimationFramewith a time accumulator provides the smoothest results. - Decouple Rendering and Physics: If you use an
external renderer such as PixiJS or standard HTML5 Canvas, you can draw
frames at the monitor's native refresh rate while limiting
Matter.Engine.update()strictly to your target physics FPS.