Handling Matter.js Physics in Background Tabs
When a user switches browser tabs, modern browsers throttle or pause
requestAnimationFrame to conserve battery and CPU
resources. For a physics engine like Matter.js, this throttling either
halts the simulation or causes a massive delta time spike when the tab
is reopened, resulting in physics "explosions" or objects tunneling
through walls. You can handle physics calculations in background tabs
through three primary methods: gracefully pausing and resuming via the
Page Visibility API, capping the engine's delta time, or moving physics
computations to a Web Worker for uninterrupted simulation.
1. Graceful Pausing with the Page Visibility API
If continuous background simulation is not required, the cleanest approach is to pause the simulation when the user leaves and resume it cleanly upon their return. This prevents massive delta time accumulation.
Use the browser's visibilitychange event to control the
Matter.Runner:
const runner = Matter.Runner.create();
const engine = Matter.Engine.create();
Matter.Runner.run(runner, engine);
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
Matter.Runner.stop(runner);
} else {
// Reset timing data to prevent delta spikes
runner.delta = 1000 / 60;
Matter.Runner.start(runner, engine);
}
});2. Clamping Delta Time (Preventing Physics Explosions)
If you use a custom animation loop rather than
Matter.Runner, a long gap between frames will pass a huge
delta value into
Matter.Engine.update(engine, delta). Enforce a maximum
delta threshold to keep the simulation stable when returning to the
tab:
let lastTime = performance.now();
const MAX_DELTA = 1000 / 30; // Max allowed step (approx. 33ms)
function loop(currentTime) {
let delta = currentTime - lastTime;
lastTime = currentTime;
// Clamp delta to avoid tunneling or explosion after background throttling
if (delta > MAX_DELTA) {
delta = MAX_DELTA;
}
Matter.Engine.update(engine, delta);
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);3. Running Continuous Physics in a Web Worker
If the simulation must continue running in real time while the tab is
hidden (such as in multiplayer or timer-sensitive games), run Matter.js
inside a Web Worker. Web Workers operate on a separate thread and are
not bound to the UI thread's requestAnimationFrame
lifecycle.
- Initialize Matter.js in the Worker: Inside the
worker script, import Matter.js and set up a fixed-interval loop using
setIntervalor a self-referencingsetTimeout. - Execute Engine Updates: Advance the engine at a
fixed rate (e.g., 60 times per second):
// Inside worker.js importScripts('matter.min.js'); const engine = Matter.Engine.create(); const timestep = 1000 / 60; setInterval(() => { Matter.Engine.update(engine, timestep); // Post body positions back to the main thread const positions = engine.world.bodies.map(body => ({ id: body.id, position: body.position, angle: body.angle })); postMessage(positions); }, timestep); - Render on the Main Thread: Use the main thread
strictly to listen for messages from the worker and update the visual
canvas via
requestAnimationFrame. When the tab is hidden, rendering stops, but the physics engine inside the worker continues unaffected.