pixijs-ticker
pixijs/pixijs-skills
Control PixiJS v8 render loop with frame-rate-independent animation and priority-based callbacks.
What is pixijs-ticker?
Manages per-frame logic in PixiJS v8 via app.ticker, which drives the render loop and executes registered callbacks with timing deltas. Use this when you need frame-independent animation, priority-ordered updates, frame-rate capping, or manual render control.
- Register callbacks with app.ticker.add() at configurable UPDATE_PRIORITY levels (HIGH, NORMAL, LOW, UTILITY)
- Access deltaTime (dimensionless ~1.0 at 60fps), deltaMS (milliseconds, speed-scaled), or elapsedMS (raw, unscaled) for different timing needs
- Cap frame rate with maxFPS and minFPS, or scale time globally with ticker.speed for slow-motion/fast-forward effects
- Bind per-object animation logic via sprite.onRender() hook, called during scene graph traversal before GPU rendering
- Use Ticker.shared (singleton, auto-protected), Ticker.system (engine background tasks), or new Ticker() instances for custom control
- Drive the render loop manually with app.ticker.update() + app.render() for headless, visibility-gated, or fixed-timestep scenarios
How to install pixijs-ticker
npx skills add https://github.com/pixijs/pixijs-skills --skill pixijs-ticker- PixiJS v8 installed
- Application instance created (app.init() called)
How to use pixijs-ticker
- 1.Create an Application and call await app.init()
- 2.Register a callback with app.ticker.add((ticker) => {...}) to run every frame
- 3.Access ticker.deltaTime for frame-rate-independent multipliers or ticker.deltaMS for millisecond-based calculations
- 4.Set UPDATE_PRIORITY as the third argument to control execution order relative to physics, animations, and rendering
- 5.Optionally set app.ticker.maxFPS, minFPS, or speed to control frame rate and time scaling
- 6.For per-object logic, assign sprite.onRender = (renderer) => {...} instead of a global callback
- 7.Call app.stop() / app.start() to pause/resume, or use autoStart: false + manual app.ticker.update() for custom loop control
Use cases
- Smooth sprite rotation/movement scaled by frame time: sprite.rotation += 0.01 * ticker.deltaTime
- Physics simulation with real-world time: position += velocity * (ticker.deltaMS / 1000)
- Priority-ordered game systems: physics at HIGH priority, animations at NORMAL, rendering at LOW
- Per-object animation tied to specific display objects using onRender callbacks
- Frame-rate capping for consistent performance on variable hardware (maxFPS = 30)
- Game developers building frame-based animation and physics loops
- PixiJS v8 users migrating from v7 (callback signature changed)
- Performance-conscious developers needing frame-rate control and priority ordering
- Developers building custom render pipelines or headless rendering scenarios
pixijs-ticker FAQ
deltaTime is a dimensionless scalar (~1.0 at 60fps) for frame-rate-independent animation. deltaMS is milliseconds (speed-scaled and capped by minFPS) for time-based calculations. elapsedMS is raw milliseconds (unscaled, uncapped) for profiling and diagnostics.
In v8, ticker callbacks receive the Ticker instance as the first argument, not a delta number. Use ticker.deltaTime, not dt directly. The v7 pattern (dt) => ... compiles but treats dt as the entire Ticker object.
Register physics callbacks at UPDATE_PRIORITY.HIGH and animations at UPDATE_PRIORITY.NORMAL (or default). Priorities execute in descending order: INTERACTION (50) > HIGH (25) > NORMAL (0) > LOW (-25) > UTILITY (-50).
Use app.ticker (created by Application) for most cases. Use Ticker.shared for cross-app synchronization (singleton, auto-protected). Create new Ticker() for independent loops (custom lifecycle, autoStart=false by default).
Set autoStart: false in app.init(), then call app.start() / app.stop() manually. Or use requestAnimationFrame with your own animate() function that calls app.ticker.update() and app.render() conditionally.
Full instructions (SKILL.md)
Source of truth, from pixijs/pixijs-skills.
name: pixijs-ticker description: "Use this skill when running per-frame logic or controlling the PixiJS v8 render loop. Covers Ticker.add/addOnce/remove, deltaTime vs deltaMS vs elapsedMS, UPDATE_PRIORITY ordering, maxFPS/minFPS capping, speed scaling, Ticker.shared vs new instances, per-object onRender hook, manual rendering. Triggers on: Ticker, UPDATE_PRIORITY, deltaTime, deltaMS, elapsedMS, onRender, app.ticker, maxFPS, minFPS, Ticker.shared." license: MIT
app.ticker runs registered callbacks every frame and drives app.render() at UPDATE_PRIORITY.LOW. Each callback receives the Ticker instance; read deltaTime as a frame-rate-independent multiplier (≈1.0 at 60fps) or deltaMS for real-time calculations.
Quick Start
app.ticker.add((ticker) => {
sprite.rotation += 0.01 * ticker.deltaTime;
sprite.x += (200 / 1000) * ticker.deltaMS;
});
app.ticker.add(
(ticker) => {
updatePhysics(ticker.deltaMS);
},
undefined,
UPDATE_PRIORITY.HIGH,
);
app.ticker.maxFPS = 30;
app.ticker.speed = 0.5;
sprite.onRender = () => {
sprite.scale.x = Math.sin(performance.now() / 500);
};
Related skills: pixijs-application (Application setup and sharedTicker option), pixijs-performance (frame rate optimization), pixijs-migration-v8 (v7 ticker signature changes).
Core Patterns
Time units
The Ticker exposes three timing values, each for different use cases:
| Property | Type | Scaled by speed? | Capped by minFPS? | Use case |
|---|---|---|---|---|
deltaTime | dimensionless (~1.0 at 60fps) | yes | yes | Frame-rate-independent animation multipliers |
deltaMS | milliseconds | yes | yes | Time-based calculations (pixels/sec) |
elapsedMS | milliseconds | no | no | Raw measurement, profiling |
import { Application } from "pixi.js";
const app = new Application();
await app.init({ width: 800, height: 600 });
app.ticker.add((ticker) => {
// deltaTime: dimensionless scalar, ~1.0 at 60fps
sprite.rotation += 0.1 * ticker.deltaTime;
// deltaMS: real milliseconds (speed-scaled, capped)
sprite.x += (200 / 1000) * ticker.deltaMS; // 200 pixels per second
// elapsedMS: raw milliseconds (no scaling, no cap)
console.log(`Raw frame time: ${ticker.elapsedMS}ms`);
});
Tension note: deltaTime is not milliseconds. It is deltaMS * Ticker.targetFPMS where targetFPMS is 0.06 (i.e. 1/16.67). At exactly 60fps, deltaTime is 1.0. At 30fps, deltaTime is 2.0. This catches people who treat it as a time value.
Priority ordering and context binding
import { Application, UPDATE_PRIORITY } from "pixi.js";
const app = new Application();
await app.init({ width: 800, height: 600 });
// INTERACTION (50) > HIGH (25) > NORMAL (0) > LOW (-25) > UTILITY (-50)
// app.render() is registered at LOW by the TickerPlugin
app.ticker.add(
(ticker) => {
// Physics runs before normal-priority callbacks
updatePhysics(ticker.deltaMS);
},
undefined,
UPDATE_PRIORITY.HIGH,
);
app.ticker.add((ticker) => {
// Default priority (NORMAL = 0), runs after HIGH but before render
updateAnimations(ticker.deltaTime);
});
// Pass `this` as the second argument to preserve context on class methods
class GameSystem {
public speed = 5;
public position = 0;
public update(ticker: Ticker): void {
this.position += this.speed * ticker.deltaTime;
}
}
const system = new GameSystem();
app.ticker.add(system.update, system);
app.ticker.remove(system.update, system); // must match both fn and context
Frame rate capping
import { Ticker } from "pixi.js";
const ticker = new Ticker();
ticker.maxFPS = 30; // Cap at 30fps (skips frames to maintain interval)
ticker.minFPS = 10; // Cap deltaTime so it never exceeds 10fps worth
// If maxFPS < minFPS, minFPS is lowered to match
// If minFPS > maxFPS, maxFPS is raised to match
maxFPS skips update calls to enforce a ceiling. minFPS caps deltaTime/deltaMS so large frame drops don't produce enormous deltas (default minFPS is 10).
Per-object onRender hook
import { Sprite, Assets, Application } from "pixi.js";
const app = new Application();
await app.init({ width: 800, height: 600 });
const texture = await Assets.load("bunny.png");
const sprite = new Sprite(texture);
app.stage.addChild(sprite);
sprite.onRender = (renderer) => {
sprite.rotation += 0.01;
};
onRender is called during scene graph traversal, before GPU rendering. It is an alternative to a global ticker callback when logic is tied to a specific display object.
Ticker.shared, Ticker.system, and new Ticker
import { Ticker, UPDATE_PRIORITY } from "pixi.js";
// Ticker.shared: singleton, autoStart=true, protected from destroy()
const shared = Ticker.shared;
// Ticker.system: separate instance used by engine background tasks,
// independent of the main scene ticker. It's a plain Ticker instance
// (autoStart=true, _protected=true) with no intrinsic priority; listeners
// are typically added at UTILITY priority by convention.
const system = Ticker.system;
// new Ticker(): custom instance, autoStart=false, you manage the lifecycle
const custom = new Ticker();
custom.autoStart = true; // start when first listener is added
custom.add((ticker) => {
console.log(ticker.deltaMS);
});
// One-shot callback that auto-removes after firing
custom.addOnce(() => console.log("fires once"), null, UPDATE_PRIORITY.NORMAL);
// When done:
custom.stop();
custom.destroy();
Application creates its own Ticker by default. Set sharedTicker: true in app.init() to use Ticker.shared instead. Ticker.shared and Ticker.system are both _protected and will not actually be destroyed if you call destroy() on them. Read ticker.FPS for the measured frame rate and ticker.count for the current listener count.
App lifecycle and manual rendering
import { Application } from "pixi.js";
const app = new Application();
await app.init({ autoStart: false });
// Pause and resume the built-in render loop at any time.
app.start();
app.stop();
// Or drive the loop yourself (headless, visibility-gated, fixed timestep, etc.)
function animate() {
app.ticker.update(); // fires registered callbacks
app.render(); // renders the stage
requestAnimationFrame(animate);
}
animate();
app.start() and app.stop() are added by the TickerPlugin and map to ticker.start() / ticker.stop(). Use autoStart: false plus your own frame driver when you need to pause on tab blur, run a fixed-timestep loop, or render offscreen.
Speed scaling
import { Application } from "pixi.js";
const app = new Application();
await app.init({ width: 800, height: 600 });
app.ticker.speed = 0.5; // Half speed (slow motion)
app.ticker.speed = 2.0; // Double speed
// speed affects deltaTime and deltaMS, but NOT elapsedMS
Common Mistakes
[CRITICAL] Ticker callback expects delta as first argument
Wrong:
app.ticker.add((dt) => {
bunny.rotation += dt;
});
Correct:
app.ticker.add((ticker) => {
bunny.rotation += ticker.deltaTime;
});
v8 passes the Ticker instance as the callback argument, not a delta number. The v7 pattern (dt) => ... compiles but dt is the entire Ticker object, so arithmetic on it produces NaN.
[HIGH] Using updateTransform for per-frame logic
Wrong:
class MySprite extends Sprite {
updateTransform() {
super.updateTransform();
this.rotation += 0.01;
}
}
Correct:
class MySprite extends Sprite {
constructor() {
super();
this.onRender = this._onRender.bind(this);
}
private _onRender() {
this.rotation += 0.01;
}
}
updateTransform was removed in v8. Use the onRender callback for per-object per-frame logic.
[MEDIUM] Treating deltaTime as milliseconds
Wrong:
app.ticker.add((ticker) => {
// Tries to move 100px/sec but deltaTime is ~1.0, not ~16.67
sprite.x += (100 * ticker.deltaTime) / 1000;
});
Correct:
app.ticker.add((ticker) => {
// Using deltaMS for time-based movement
sprite.x += (100 / 1000) * ticker.deltaMS;
// Or using deltaTime as a frame-rate multiplier
sprite.x += 1.5 * ticker.deltaTime;
});
deltaTime is a dimensionless scalar (~1.0 at 60fps), not milliseconds. Use deltaMS for real time calculations. Use deltaTime as a simple multiplier when you want "per frame at 60fps" behavior.
API Reference
Related skills
More from pixijs/pixijs-skills and the wider catalog.

pixijs
Router skill for PixiJS v8—directs to specialized sub-skills for rendering, scene objects, assets, and advanced graphics.

pixijs-accessibility
Add screen reader and keyboard navigation to PixiJS v8 apps via AccessibilitySystem.

pixijs-application
Create and configure PixiJS v8 Applications with renderers, stage, and lifecycle management.

pixijs-assets
Load and manage PixiJS v8 resources with format detection, bundles, caching, and progress tracking.

mysql
Plan and review MySQL/InnoDB schema, indexing, query tuning, transactions, and operations.

neki
Overview and information about Neki, the sharded Postgres product by PlanetScale. Load when working with Neki-related tasks and the need to scale or shard postgres. Load when facing Postgres scaling or sharding issues.