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.

  1. Initialize Matter.js in the Worker: Inside the worker script, import Matter.js and set up a fixed-interval loop using setInterval or a self-referencing setTimeout.
  2. 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);
  3. 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.