liabru/matter-js · error · Error
Matter.Runner: missing required global window.cancelAnimatio
Error message
Matter.Runner: missing required global window.cancelAnimationFrame.
What it means
Runner._cancelNextFrame cancels a scheduled frame using window.cancelAnimationFrame, which must exist to stop the runner loop safely. When window or cancelAnimationFrame is unavailable (Node, Worker, jsdom), Runner.stop()/pause cannot cancel the pending callback and throws. It is the teardown counterpart of error 0.
Source
Thrown at src/core/Runner.js:250
runner.frameRequestId = window.requestAnimationFrame(callback);
} else {
throw new Error('Matter.Runner: missing required global window.requestAnimationFrame.');
}
return runner.frameRequestId;
};
/**
* Cancels the last callback scheduled by `Runner._onNextFrame` on this `runner`.
* @private
* @method _cancelNextFrame
* @param {runner} runner
*/
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.');
}
};
/**
* Returns the mean of the given numbers.
* @method _mean
* @private
* @param {Number[]} values
* @return {Number} the mean of given values.
*/
var _mean = function(values) {
var result = 0,
valuesLength = values.length;
for (var i = 0; i < valuesLength; i += 1) {
result += values[i];
}
View on GitHub (pinned to acb99b6f87)
Solutions
- Ensure the environment provides both requestAnimationFrame AND cancelAnimationFrame on window.
- Polyfill both: window.cancelAnimationFrame = id => clearTimeout(id) alongside the RAF polyfill.
- Store the frameRequestId returned by Runner.run and call window.cancelAnimationFrame yourself in a compatible environment.
- In browsers, this should never occur — verify code is not accidentally running in a Worker context.
Example fix
// before (Node.js test) Matter.Runner.stop(runner); // throws // after window.cancelAnimationFrame = id => clearTimeout(id); // polyfill first Matter.Runner.stop(runner);
Defensive patterns
Strategy: fallback
Validate before calling
if (typeof window === 'undefined' || typeof window.cancelAnimationFrame !== 'function') {
globalThis.window = globalThis.window || {};
window.cancelAnimationFrame = window.cancelAnimationFrame || (id => clearTimeout(id));
} Type guard
function canCancelRaf(env) {
return typeof env !== 'undefined' && env !== null && typeof env.cancelAnimationFrame === 'function';
} Try / catch
try {
Matter.Runner.stop(runner);
} catch (e) {
if (String(e.message).includes('cancelAnimationFrame') && runner.frameRequestId) {
clearTimeout(runner.frameRequestId);
} else { throw e; }
} Prevention
- Polyfill cancelAnimationFrame together with requestAnimationFrame
- Track runner.frameRequestId yourself for manual cleanup in headless runs
- Confirm the code path stopping the runner is the same environment that started it
When it happens
Trigger: Calling Runner.stop(runner) or Runner.pause(runner) in an environment lacking window.cancelAnimationFrame — typically Node, Web Worker, or jsdom tests.
Common situations: Stopping a runner in server-side code after a polyfilled start; cleaning up tests without a full RAF polyfill (started but cannot cancel).
Related errors
AI-assisted analysis of liabru/matter-js@acb99b6f87 (2026-09-02).
Data as JSON: /api/errors/b94ba01bbc7b292d.
Report an issue: GitHub.