SKILL.md
$27
gif.anchor.set(0.5);
gif.x = app.screen.width / 2;
gif.y = app.screen.height / 2;
app.stage.addChild(gif);
> [!NOTE]
> GIFs decode every frame into a separate canvas texture. For performance-critical animations with many frames, prefer a spritesheet with `AnimatedSprite` — it uses a single atlas texture and batches better on the GPU.
**Related skills:** `pixijs-scene-core-concepts` (scene graph basics), `pixijs-scene-sprite` (`AnimatedSprite` for spritesheet-based animation), `pixijs-assets` (`Assets.load`, caching, unloading), `pixijs-ticker` (frame timing), `pixijs-performance` (texture memory).
## Constructor options
`GifSpriteOptions` extends `Omit<SpriteOptions, 'texture'>`; `texture` is managed internally (set from `source.textures[0]` and swapped per frame). All other `Sprite` options (`anchor`, `scale`, `tint`, `roundPixels`, etc.) are valid, and all `Container` options (`position`, `scale`, `tint`, `label`, `filters`, `zIndex`, etc.) are also valid here — see `skills/pixijs-scene-core-concepts/references/constructor-options.md`.
Leaf-specific options added by `GifSpriteOptions`:
| Option | Type | Default | Description |
| ---------------- | --------------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------- |
| `source` | `GifSource` | — | Required. The parsed GIF data returned by `Assets.load('file.gif')`. Can be shared across multiple `GifSprite` instances. |
| `autoPlay` | `boolean` | `true` | Start playback immediately on construction. If `false`, you must call `gif.play()` to begin. |
| `loop` | `boolean` | `true` | Repeat the animation on reaching the last frame. When `false`, the sprite stops at the final frame and fires `onComplete`. |
| `animationSpeed` | `number` | `1` | Multiplier on the GIF's native frame timing. `2` runs at double speed; `0.5` runs at half. |
| `autoUpdate` | `boolean` | `true` | Connect playback to `Ticker.shared`. Set to `false` to drive updates yourself via `gif.update(ticker)`. |
| `fps` | `number` | `30` | Fallback frame rate for GIFs that do not specify per-frame delays. |
| `onComplete` | `() => void \| null` | `null` | Called when a non-looping animation reaches the last frame. |
| `onLoop` | `() => void \| null` | `null` | Called each time a looping animation wraps around. |
| `onFrameChange` | `(frame: number) => void \| null` | `null` | Called every time the displayed frame index changes. |
| `scaleMode` | `SCALE_MODE` | `'linear'` | Deprecated since 8.13.0 — pass `scaleMode` via `Assets.load(..., { data: { scaleMode } })` instead. |
The constructor also accepts a bare `GifSource` as its sole argument (`new GifSprite(source)`), which is shorthand for `new GifSprite({ source })` using the defaults above.
## Core Patterns
### Setup and the side-effect import
import "pixi.js/gif";
import { Assets } from "pixi.js";
import { GifSprite } from "pixi.js/gif";
const source = await Assets.load("animation.gif");
const gif = new GifSprite({ source });
`pixi.js/gif` calls `extensions.add(GifAsset)`, registering `.gif` with the asset loader. Without it, `Assets.load` does not recognize GIF files. `GifSprite` and `GifSource` are exported from `pixi.js/gif`, not `pixi.js`.
Importing a named export from `pixi.js/gif` also triggers the side effect, so a bare `import 'pixi.js/gif'` is only needed when you don't import anything from that path.
### Playback control
const gif = new GifSprite({ source });
gif.play();
gif.stop();
gif.currentFrame = 5;
gif.animationSpeed = 2;
gif.animationSpeed = 0.5;
gif.playing; // read-only
gif.progress; // 0-1 playback position
gif.totalFrames; // number of frames
gif.duration; // total duration in ms
`autoPlay: true` (default) starts playback immediately; `loop: true` (default) repeats. `animationSpeed` is a multiplier on the GIF's native frame timing. `currentFrame` is zero-based.
### Loading options
const source = await Assets.load({
src: "animation.gif",
data: {
fps: 12,
scaleMode: "nearest",
resolution: 2,
},
});
const fromDataUri = await Assets.load("data:image/gif;base64,R0lGODlh...");
Options in `data` are passed to `GifSource.from`. `fps` sets the fallback frame delay for GIFs that don't specify timing. `scaleMode` and `resolution` control the canvas textures created for each frame. The loader matches both `.gif` file extensions and `data:image/gif` URIs.
### Callbacks
const gif = new GifSprite({
source,
loop: false,
onComplete: () => console.log("animation finished"),
onLoop: () => console.log("loop completed"),
onFrameChange: (frame) => console.log("now on frame", frame),
});
- `onComplete` fires when a non-looping animation reaches the last frame.
- `onLoop` fires each time a looping animation wraps around.
- `onFrameChange` fires every time the displayed frame changes.
### Manual update mode
const gif = new GifSprite({ source, autoUpdate: false });
app.ticker.add((ticker) => {
gif.update(ticker);
});
`autoUpdate: false` disconnects from `Ticker.shared`. You call `gif.update(ticker)` yourself, passing any `Ticker` instance. Useful when animation should be driven by a private ticker (e.g., a pause-aware game ticker).
### Sharing source data and cloning
const source = await Assets.load("animation.gif");
const gif1 = new GifSprite({ source, autoPlay: true });
const gif2 = new GifSprite({ source, autoPlay: false });
const gif3 = gif1.clone();
gif3.animationSpeed = 0.5;
`GifSource` can be shared across multiple `GifSprite` instances; each sprite has independent playback state. `clone()` copies all playback settings but creates an independent instance.
## Common Mistakes
### [HIGH] Not importing pixi.js/gif
Wrong:
import { Assets } from "pixi.js";
const gif = await Assets.load("animation.gif");
Correct:
import "pixi.js/gif";
import { Assets } from "pixi.js";
const source = await Assets.load("animation.gif");
The GIF loader extension must be registered before loading. Without the side-effect import, the loader does not recognize `.gif` files and the load either fails or returns raw data.
### [MEDIUM] Expecting Assets.load to return a Texture
Wrong:
const texture = await Assets.load("animation.gif");
const sprite = new Sprite(texture);
Correct:
const source = await Assets.load("animation.gif");
const gif = new GifSprite({ source });
`Assets.load` on a GIF returns a `GifSource` containing frame textures and timing data. Pass the source to `GifSprite`; for a single still frame, read `source.textures[0]`.
### [MEDIUM] GIF memory not released on destroy
Wrong:
gif.destroy();
// GifSource and frame textures remain in memory
Correct:
gif.destroy(true);
// or
await Assets.unload("animation.gif");
GIF frames hold decoded pixel data as individual canvas textures. `gif.destroy()` (or `destroy(false)`) destroys the sprite but keeps the `GifSource` intact. Pass `true` to also destroy the source. For shared sources, only destroy when the last consumer is done, or call `Assets.unload` to let the asset cache handle it.
### [LOW] Do not nest children inside a GifSprite
`GifSprite` extends `Sprite`, which sets `allowChildren = false`. It is a leaf. To group a GIF with other display objects, wrap them all in a plain `Container`:
const group = new Container();
group.addChild(gif, label);