phaserjs/phaser · error · Error

Matter.Runner: missing required global window.requestAnimati

Error message

Matter.Runner: missing required global window.requestAnimationFrame.

What it means

Runner.js:200 throws from `_onNextFrame` when `typeof window === 'undefined'` or `window.requestAnimationFrame` is missing. Matter's Runner schedules each physics tick via rAF; without it the loop cannot advance. This is Matter.js's hard dependency on a browser-like global.

Source

Thrown at src/physics/matter-js/lib/core/Runner.js:200

     * If you wish to only temporarily pause the engine, see `engine.enabled` instead.
     * @method stop
     * @param {runner} runner
     */
    Runner.stop = function(runner) {
        Runner._cancelNextFrame(runner);
    };

    /**
     * Alias for `Runner.run`.
     * @method start
     * @param {runner} runner
     * @param {engine} engine
     */
    Runner._onNextFrame = function(runner, callback) {
        if (typeof window !== 'undefined' && window.requestAnimationFrame) {
            runner.frameRequestId = window.requestAnimationFrame(callback);
        } else {
            throw new Error('Matter.Runner: missing required global window.requestAnimationFrame.');
        }
        return runner.frameRequestId;
    };
    Runner._cancelNextFrame = function(runner) {
        if (typeof window !== 'undefined' && window.cancelAnimationFrame) {
            window.cancelAnimationFrame(runner.frameRequestId);
        } else {
            throw new Error('Matter.Runner: missing required global window.cancelAnimationFrame.');
        }
    };

    var _mean = function(values) {
        var result = 0,
            valuesLength = values.length;
        for (var i = 0; i < valuesLength; i += 1) {
            result += values[i];
        }
        return (result / valuesLength) || 0;

View on GitHub (pinned to 41be1e462b)

Solutions

  1. Run physics only in a browser context; for Node use a manual `Runner.tick(engine, delta)` loop or `Engine.update(engine, delta)` instead of `Runner.run`.
  2. Polyfill `globalThis.requestAnimationFrame` (e.g. via a timer-based shim) before starting the Runner.
  3. Use Phaser's HEADLESS mode for server stepping and avoid the Matter Runner's rAF scheduling.

Example fix

// before
Matter.Runner.create(engine) // then Runner.run under Node -> throws
// after (Node)
setInterval(() => Matter.Engine.update(engine, 1000 / 60), 1000 / 60)
Defensive patterns

Strategy: validation

Validate before calling

const hasRAF = typeof window !== 'undefined' && typeof window.requestAnimationFrame === 'function'
if (!hasRAF) {
  // Node/SSR: step manually instead of Runner.run
  setInterval(() => Matter.Engine.update(engine, 1000 / 60), 1000 / 60)
} else {
  Matter.Runner.run(runner, engine)
}

Type guard

const supportsRAF = () => typeof window !== 'undefined' && typeof window.requestAnimationFrame === 'function'

Prevention

When it happens

Trigger: Running `Matter.Runner.run(runner, engine)` (or the Phaser Matter Physics system that uses it) in Node.js, a Web Worker without rAF polyfill, jsdom, or an SSR context where `window.requestAnimationFrame` is undefined.

Common situations: Server-side simulation/SSR that imports Phaser's Matter physics; unit tests in Node; rendering on the server with intent to step physics manually; bundlers that tree-shake the DOM globals.

Related errors


AI-assisted analysis of phaserjs/phaser@41be1e462b (2026-08-13). Data as JSON: /api/errors/6058b28ed517efd6. Report an issue: GitHub.