How requestAnimationFrame Affects Matter.js Tick Rate
This article explores how the browser's
requestAnimationFrame API directly governs the execution
and tick rate of physics simulations in Matter.js. You will learn how
browser rendering loops synchronize with the Matter.js engine, why
varying monitor refresh rates and dropped frames alter simulation
behavior, and how to stabilize physics calculations across different
hardware environments.
The Connection Between requestAnimationFrame and Matter.js
By default, Matter.js delegates its timing logic to
Matter.Runner, which relies on
window.requestAnimationFrame (rAF) to synchronize
simulation updates with the browser’s render cycle. When you start an
engine with Matter.Runner.run(runner, engine), the runner
invokes requestAnimationFrame on a continuous loop. On each
frame, it calculates the elapsed time (delta) since the previous call
and passes this value to Engine.update(engine, delta).
Because requestAnimationFrame is tethered to the display
subsystem of the user's device, the tick rate of Matter.js is
fundamentally tied to the client's screen refresh rate rather than a
guaranteed internal timer.
Display Refresh Rates and Tick Inconsistency
The frequency at which requestAnimationFrame executes
varies significantly across devices:
- Standard displays (60Hz): Fires roughly every 16.67 milliseconds (~60 ticks per second).
- High-refresh displays (120Hz, 144Hz, 240Hz): Fires every 8.33ms, 6.94ms, or 4.16ms respectively.
- Low-power or throttled states (30Hz or variable): Fires at fluctuating, lower rates.
If Matter.Runner is set to run with a dynamic delta
based strictly on the timestamp provided by
requestAnimationFrame, a 120Hz monitor will run twice as
many update cycles per second as a 60Hz monitor. While a dynamically
calculated delta attempts to compensate by stepping the physics by
smaller increments on faster displays, numerical integration in physics
engines (specifically Verlet or Euler integration) is not completely
scale-invariant. Consequently, springs, constraints, and friction can
behave differently at 120Hz compared to 60Hz.
Frame Drops, Background Tabs, and the "Spiral of Death"
Using requestAnimationFrame means that the tick rate
drops whenever the main thread is blocked or the browser tab loses
focus.
- Background Throttling: Browsers pause or throttle
requestAnimationFramecalls to 1Hz or lower when a user switches tabs to conserve battery and CPU resources. When the tab regains focus, the delta time between the previous frame and the current frame can be hundreds or thousands of milliseconds. If passed directly toEngine.update(), this massive delta can launch bodies through walls (tunneling) or tear constraints apart. - Delta Clamping:
Matter.Runnermitigates large jumps by enforcing an upper bound on the delta (controlled byrunner.isFixedand default clamping mechanics), discarding excessively large time jumps. However, this causes the physics simulation to appear in slow motion during severe performance dips rather than maintaining real-world time synchronization.
Achieving Consistent Physics with a Fixed Timestep
To eliminate hardware-dependent physics discrepancies caused by
requestAnimationFrame, you can decouple the physics step
from the visual frame rate using a fixed timestep with an
accumulator.
Instead of allowing requestAnimationFrame to pass
arbitrary delta values directly into Engine.update(), use
requestAnimationFrame solely for rendering and tracking
accumulated real time:
const engine = Matter.Engine.create();
const fixedDelta = 1000 / 60; // Exact 60Hz physics step (16.66ms)
let lastTime = performance.now();
let accumulator = 0;
function loop(currentTime) {
const frameTime = Math.min(currentTime - lastTime, 100); // Clamp maximum frame time
lastTime = currentTime;
accumulator += frameTime;
while (accumulator >= fixedDelta) {
Matter.Engine.update(engine, fixedDelta);
accumulator -= fixedDelta;
}
// Trigger visual rendering here
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);Using this pattern, requestAnimationFrame still drives
the drawing loop, but the physics engine consistently computes
deterministic updates regardless of whether the user is on a 60Hz office
monitor or a 240Hz gaming display.