phaserjs/phaser · error · Error

Matter.Runner: missing required global window.cancelAnimatio

Error message

Matter.Runner: missing required global window.cancelAnimationFrame.

What it means

Runner.js:208 throws from `_cancelNextFrame` when `window.cancelAnimationFrame` is missing. The Runner calls this to cancel a pending frame on stop/pause; without it cleanup cannot proceed safely and Matter throws rather than leak a scheduled callback.

Source

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

    /**
     * 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;
    };
    Runner._mean = _mean;
    /*
    *
    *  Events Documentation
    *
    */

View on GitHub (pinned to 41be1e462b)

Solutions

  1. Polyfill `globalThis.cancelAnimationFrame` (e.g. via `clearTimeout`) before using the Runner.
  2. Avoid `Runner.run`/`Runner.stop` under Node — drive `Engine.update` manually and skip frame scheduling entirely.
  3. Ensure both rAF and cRAF are present together when polyfilling.

Example fix

// before (partial shim has rAF but not cRAF)
Runner.stop(runner) // throws
// after
globalThis.cancelAnimationFrame = globalThis.cancelAnimationFrame || (id => clearTimeout(id))
Runner.stop(runner)
Defensive patterns

Strategy: validation

Validate before calling

if (typeof window === 'undefined' || typeof window.cancelAnimationFrame !== 'function') {
  globalThis.cancelAnimationFrame = globalThis.cancelAnimationFrame || (id => clearTimeout(id))
}
Runner.stop(runner)

Type guard

const supportsCancelRAF = () => typeof window !== 'undefined' && typeof window.cancelAnimationFrame === 'function'

Prevention

When it happens

Trigger: Calling `Runner.stop(runner)` or `Runner.pause(runner)` (or Phaser's Matter physics shutdown) in an environment where `window.requestAnimationFrame` exists but `cancelAnimationFrame` does not, or where neither exists (Node/jsdom).

Common situations: Stopping a Matter Runner in a partial browser shim (some test harnesses define rAF but not cRAF); SSR teardown; an environment where `window` exists but is incompletely polyfilled.

Related errors


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