At the core of PixiJS lies its render loop, a repeating cycle that updates and redraws your scene every frame. Unlike traditional web development where rendering is event-based (e.g. on user input), PixiJS uses a continuous animation loop that provides full control over real-time rendering.
This guide provides a deep dive into how PixiJS structures this loop internally, from the moment a frame begins to when it is rendered to the screen. Understanding this will help you write more performant, well-structured applications.
Each frame, PixiJS performs the following sequence:
This cycle repeats as long as your application is running and its ticker is active.
The render loop is driven by the Ticker class, which uses requestAnimationFrame to schedule work. Each tick:
minFPS and maxFPSticker.add() or app.ticker.add()app.ticker.add((ticker) => {
bunny.rotation += ticker.deltaTime * 0.1;
});
Every callback receives the current Ticker instance. You can access ticker.deltaTime (scaled frame delta) and ticker.elapsedMS (unscaled delta in ms) to time animations.
PixiJS uses a hierarchical scene graph to represent all visual objects. Before rendering, the engine traverses the tree to:
onRender callbacks on individual display objectsThe onRender hook lets you attach per-frame logic directly to a display object, as an alternative to a global ticker callback:
sprite.onRender = () => {
sprite.rotation += 0.01;
};
Once the scene graph is ready, the renderer walks the display list starting at app.stage:
All rendering is retained mode: objects persist across frames unless explicitly removed.
Rendering is done via either WebGL or WebGPU, depending on your environment. The renderer abstracts away the differences behind a common API.
requestAnimationFrame
│
[Ticker._tick()]
│
├─ Compute elapsed time
├─ Call user ticker listeners
├─ Update world transforms
│ └─ sprite.onRender (called per-object during traversal)
├─ Cull display objects (if enabled)
└─ Render stage
├─ Traverse display list
├─ Upload data to GPU
└─ Draw
You can pause the loop by stopping the ticker, and resume it later:
app.ticker.stop(); // Pause rendering
app.ticker.start(); // Resume rendering
To cap the frame rate (useful for reducing battery usage on mobile):
app.ticker.maxFPS = 30; // Cap at 30 FPS
For manual rendering without a ticker at all:
app.ticker.stop();
function customLoop() {
requestAnimationFrame(customLoop);
app.renderer.render(app.stage);
}
customLoop();