pixijs-ticker

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,…

INSTALLATION
npx skills add https://github.com/pixijs/pixijs-skills --skill pixijs-ticker
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$27

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;

});

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card