PixiJS v8 + Svelte 5
Stack: SvelteKit 2, Svelte 5 (runes), PixiJS v8.17+ (WebGL2/WebGPU).
Install: npm install pixi.js
Integration approach: plain PixiJS + Svelte onMount
Use PixiJS directly — do NOT use svelte-pixi for imperative rendering apps
(document viewers, annotation tools, data visualization). svelte-pixi is only
useful when your scene graph maps 1:1 to a Svelte component tree, which is rare
for performance-sensitive apps with dynamic data.
Why plain PixiJS over svelte-pixi
- Fewer dependencies — library consumers only need
pixi.js, not
svelte-pixi
- Imperative rendering — annotation/data layers build Graphics from data
arrays in loops, not from component trees
- Event control — precise listener ordering (e.g. pan vs draw tool) requires
direct
addEventListener
- Library-friendly — if packaging as npm library, fewer deps = easier
adoption
SvelteKit integration pattern
<script lang="ts">
import { browser } from '$app/environment';
import { onMount } from 'svelte';
import { Application } from 'pixi.js';
let containerEl: HTMLDivElement;
onMount(() => {
if (!browser) return;
const app = new Application();
(async () => {
await app.init({
resizeTo: containerEl,
preference: 'webgpu',
backgroundAlpha: 0,
antialias: true,
resolution: window.devicePixelRatio || 1,
autoDensity: true,
});
containerEl.appendChild(app.canvas);
// Create plugins, set up scene...
})();
return () => app.destroy(true, { children: true });
});
</script>
<div bind:this={containerEl} class="h-full w-full overflow-hidden"></div>
Key points:
- SSR safety:
onMount only runs in browser. Add if (!browser) return
guard.
- Async init:
await app.init() — v8 requires this, not constructor
options.
app.canvas not app.view — v8 change.
- Cleanup: return cleanup function from
onMount to destroy app.
resizeTo: pass the container element for auto-resize.
Reference files
All in references/ relative to this file. Read the relevant file(s) before
writing code.
Primary
| File |
Contents |
examples.md |
Read when writing code. Copy-paste v8 patterns: Graphics API, events/drag, masks, filters, RenderLayer, text, mesh/shaders, DOMContainer, RenderTexture. |
ecosystem.md |
Read for layout, UI, extensions. @pixi/layout (Yoga flexbox), @pixi/ui, extensions system, responsive/resize patterns, viewport pan/zoom. |
core-concepts.md |
Performance tips, render pipeline, scene graph, GC, RenderGroups, RenderLayers, culling |
container-and-graphics.md |
Container API, v8 Graphics API deep dive (fills, strokes, SVG paths, pixel lines) |
Secondary
| File |
Contents |
svelte-pixi.md |
svelte-pixi wrapper library (optional — use only for simple declarative scenes) |
scene-objects.md |
Sprite, Mesh, NineSliceSprite, ParticleContainer |
text.md |
Text, BitmapText, HTMLText, SplitText, TextStyle, TilingSprite |
interaction-and-effects.md |
Events/hit-testing, accessibility, filters, blend modes, color, math |
assets.md |
Asset pipeline, bundles/manifests, compressed textures, SVG rendering |
textures-and-ticker.md |
Texture system, Ticker/frame timing |
renderers.md |
WebGL/WebGPU/Canvas renderer selection, CacheAsTexture |
application.md |
Application class, resize plugin, ticker plugin |
advanced-integration.md |
Mixing PixiJS + Three.js |
API lookup
api-reference-toc.md → index of 93 exported classes with line numbers →
api-reference.md (22K lines, use view with line ranges).
Critical v8 differences
These patterns changed from v7. Claude's training data likely reflects v7 and
will generate wrong code without these.
Initialization & structure
- Async init:
await app.init(options) — not constructor options
app.canvas not app.view
- All constructors use options objects:
new BlurFilter({ blur: 8 }) not
new BlurFilter(8). Applies to Text, BitmapText, HTMLText, Mesh, MeshPlane,
NineSliceSprite, TilingSprite, DisplacementFilter, etc.
- Single package:
import { X } from 'pixi.js' — no more @pixi/sprite,
@pixi/app etc.
skipExtensionImports: tree-shaking via selective import 'pixi.js/app'
etc.
Scene graph
- Leaf nodes cannot have children: Sprite, Graphics, Mesh, Text,
TilingSprite, HTMLText cannot
addChild(). Only Container can. Wrap in
Container if nesting needed.
container.label replaces container.name
container.origin: new property, CSS-like transform-origin (distinct from
pivot — origin doesn't shift position)
onRender replaces updateTransform:
container.onRender = () => { ... } — the old override pattern no longer runs
per frame
getBounds() returns Bounds, not Rectangle — use
container.getBounds().rectangle
reparentChild(): preserves world transform when moving between
containers
Rendering & display
- Graphics: chained
.rect().fill().stroke() — no beginFill/endFill
- GraphicsContext: reusable shared geometry —
new Graphics(context) where
multiple Graphics share one context
- RenderLayer: new class for rendering children above filtered containers
(
layer.attach(child))
- DOMContainer: new class for embedding HTML elements in the scene graph
with transforms
- CullerPlugin: opt-in —
extensions.add(CullerPlugin). Set
cullable = true on containers.
cacheAsTexture() replaces cacheAsBitmap. Call updateCacheTexture()
after changes.
Text
- Text:
new Text({ text, style }) options object — not positional args
- Tagged text:
style.tagStyles for inline <bold> style tags (new in
v8.16)
- SplitText/SplitBitmapText: per-character/word/line animation containers
(experimental)
- GifSprite: new, from
import { GifSprite } from 'pixi.js/gif'
Assets & textures
- Assets:
await Assets.load() — PIXI.Loader removed entirely
Assets.add({ alias, src }) — object form, not positional args
- Textures must be loaded before use —
Texture.from() only works for
already-loaded assets. For images from API routes (no file extension), load
via HTMLImageElement then Texture.from(img).
SCALE_MODES → strings: 'nearest', 'linear' (not
SCALE_MODES.NEAREST)
WRAP_MODES → strings: 'repeat', 'clamp-to-edge', 'mirror-repeat'
Events & interaction
- Default
eventMode is passive — must set 'static' or 'dynamic' for
interactive objects
- Ticker callback passes
Ticker instance, not delta — use
ticker.deltaTime, ticker.elapsedMS
Masks & filters
- Masks:
setMask({ mask, inverse }) for inverse masks
- Filters:
filter.antialias replaces filter.resolution
- Custom filters:
new Filter({ glProgram: GlProgram.from({...}), resources: {...} }) —
textures are resources, not uniforms
- Community filters:
import { X } from 'pixi-filters/x' not
@pixi/filter-x
Particles
- ParticleContainer uses
Particle, not Sprite:
addParticle()/removeParticle() — particles have
scaleX/scaleY/anchorX/anchorY/color, not the full Container API.
Stored in particleChildren, not children.
Misc
utils removed — direct imports: import { isMobile } from 'pixi.js'
settings removed — use AbstractRenderer.defaultOptions or pass to init
Design decisions for PixiJS + SvelteKit
Architecture: plugins as pure TS classes
Keep PixiJS logic in pure TypeScript classes (plugins) with no Svelte or DOM
imports. The Svelte component is a thin wrapper that creates the Application,
instantiates plugins, and exposes reactive state via $bindable props.
PixiCanvas.svelte — thin Svelte wrapper (onMount, props, context)
├── ImagePlugin.ts — loads images, zoom/pan, viewport transforms
├── ArrowDataPlugin.ts — Arrow Table → batched Graphics rendering
└── AnnotationPlugin.ts — drawing state machine, hit testing
This pattern makes plugins:
- Testable with Vitest without a browser
- Reusable across different Svelte components
- Publishable as a standalone library
SSR safety
PixiJS requires DOM + WebGL/WebGPU. Use onMount (never runs on server) with a
browser guard, or export const ssr = false on the route.
Performance scaling
| Object count |
Approach |
| < 100 |
Individual Graphics objects per item |
| 100–5000 |
Batch by category (e.g. one Graphics per status color = 5 draw calls for 5000 annotations) |
| 5000+ |
Add viewport culling, isRenderGroup, cacheAsTexture |
| 10000+ |
ParticleContainer with Particle instances |
Additional levers: isRenderGroup (GPU container transforms), cacheAsTexture
(flatten static subtrees), cullable, RenderLayer (UI above filters), object
pooling, GraphicsContext (shared geometry).
Viewport pan/zoom pattern
Use a Container with isRenderGroup: true as the viewport. Apply scale/position
transforms to the stage or viewport container. Handle wheel zoom centered on
cursor position.
// Zoom toward cursor
const factor = e.deltaY > 0 ? 1 / 1.1 : 1.1;
const newZoom = Math.max(0.05, Math.min(50, zoom * factor));
panX = mouseX - (mouseX - panX) * (newZoom / zoom);
panY = mouseY - (mouseY - panY) * (newZoom / zoom);
zoom = newZoom;
stage.scale.set(zoom);
stage.position.set(panX, panY);
Loading images from API routes (no file extension)
Assets.load() uses URL extension to detect format. API routes like
/api/images/page-001 have no extension. Load via HTMLImageElement instead:
const img = new Image();
img.crossOrigin = "anonymous";
img.src = url;
await new Promise((resolve, reject) => {
img.onload = resolve;
img.onerror = reject;
});
const texture = Texture.from(img);
Graphics batching (Rerun-inspired columnar rendering)
For data-driven rendering (annotations, overlays), batch shapes by visual
property (e.g. color) into a single Graphics object per group. This reduces GPU
draw calls from N to the number of groups.
// 5 draw calls instead of 5000
for (const [color, rowIndices] of colorGroups) {
const g = graphicsMap.get(color);
g.clear();
for (const i of rowIndices) {
g.rect(xCol[i], yCol[i], wCol[i], hCol[i]);
}
g.fill({ color, alpha: 0.15 });
g.stroke({ color, width: 2 });
}
Routing
| Task |
Read |
| Set up pixi in SvelteKit |
This file (integration pattern above) |
| Click/drag/pointer events |
examples.md → Events |
| Draw shapes/paths/curves |
examples.md → Graphics API, then container-and-graphics.md |
| Filters or custom shaders |
examples.md → Filters / Mesh & Shaders, then interaction-and-effects.md |
| Text/labels/fonts |
examples.md → Text, then text.md |
| Load textures/spritesheets |
assets.md, then examples.md → Asset Loading |
| Responsive layout / flexbox |
ecosystem.md → @pixi/layout |
| Buttons / sliders / UI widgets |
ecosystem.md → @pixi/ui |
| Responsive resize / scaling |
ecosystem.md → Responsive Patterns |
| Pan / zoom / viewport |
ecosystem.md → Viewport Patterns |
| Custom extensions |
ecosystem.md → Extensions System |
| Slow rendering / optimization |
core-concepts.md → Performance Tips |
| 10k+ objects |
core-concepts.md → Performance Tips + ParticleContainer |
| UI above filtered content |
examples.md → RenderLayer |
| Declarative scene (simple) |
svelte-pixi.md (optional, for simple component-tree scenes only) |
| Specific class API |
api-reference-toc.md → line range in api-reference.md |
1---2name: building-pixijs3description: PixiJS v8 + Svelte 5/SvelteKit 2 for 2D graphics: rendering, Graphics API, events, filters, viewport pan/zoom, @pixi/layout, @pixi/ui, performance optimization. For document viewers, annotation tools, data visualization.4---56# PixiJS v8 + Svelte 578Stack: SvelteKit 2, Svelte 5 (runes), PixiJS v8.17+ (WebGL2/WebGPU).910Install: `npm install pixi.js`1112## Integration approach: plain PixiJS + Svelte onMount1314Use PixiJS directly — **do NOT use svelte-pixi** for imperative rendering apps15(document viewers, annotation tools, data visualization). svelte-pixi is only16useful when your scene graph maps 1:1 to a Svelte component tree, which is rare17for performance-sensitive apps with dynamic data.1819### Why plain PixiJS over svelte-pixi2021- **Fewer dependencies** — library consumers only need `pixi.js`, not22 `svelte-pixi`23- **Imperative rendering** — annotation/data layers build Graphics from data24 arrays in loops, not from component trees25- **Event control** — precise listener ordering (e.g. pan vs draw tool) requires26 direct `addEventListener`27- **Library-friendly** — if packaging as npm library, fewer deps = easier28 adoption2930### SvelteKit integration pattern3132```svelte33<script lang="ts">34 import { browser } from '$app/environment';35 import { onMount } from 'svelte';36 import { Application } from 'pixi.js';3738 let containerEl: HTMLDivElement;3940 onMount(() => {41 if (!browser) return;42 const app = new Application();4344 (async () => {45 await app.init({46 resizeTo: containerEl,47 preference: 'webgpu',48 backgroundAlpha: 0,49 antialias: true,50 resolution: window.devicePixelRatio || 1,51 autoDensity: true,52 });53 containerEl.appendChild(app.canvas);54 // Create plugins, set up scene...55 })();5657 return () => app.destroy(true, { children: true });58 });59</script>6061<div bind:this={containerEl} class="h-full w-full overflow-hidden"></div>62```6364Key points:6566- **SSR safety**: `onMount` only runs in browser. Add `if (!browser) return`67 guard.68- **Async init**: `await app.init()` — v8 requires this, not constructor69 options.70- **`app.canvas`** not `app.view` — v8 change.71- **Cleanup**: return cleanup function from `onMount` to destroy app.72- **`resizeTo`**: pass the container element for auto-resize.7374## Reference files7576All in `references/` relative to this file. **Read the relevant file(s) before77writing code.**7879### Primary8081| File | Contents |82| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |83| **`examples.md`** | **Read when writing code.** Copy-paste v8 patterns: Graphics API, events/drag, masks, filters, RenderLayer, text, mesh/shaders, DOMContainer, RenderTexture. |84| **`ecosystem.md`** | **Read for layout, UI, extensions.** `@pixi/layout` (Yoga flexbox), `@pixi/ui`, extensions system, responsive/resize patterns, viewport pan/zoom. |85| `core-concepts.md` | Performance tips, render pipeline, scene graph, GC, RenderGroups, RenderLayers, culling |86| `container-and-graphics.md` | Container API, v8 Graphics API deep dive (fills, strokes, SVG paths, pixel lines) |8788### Secondary8990| File | Contents |91| ---------------------------- | ------------------------------------------------------------------------------- |92| `svelte-pixi.md` | svelte-pixi wrapper library (optional — use only for simple declarative scenes) |93| `scene-objects.md` | Sprite, Mesh, NineSliceSprite, ParticleContainer |94| `text.md` | Text, BitmapText, HTMLText, SplitText, TextStyle, TilingSprite |95| `interaction-and-effects.md` | Events/hit-testing, accessibility, filters, blend modes, color, math |96| `assets.md` | Asset pipeline, bundles/manifests, compressed textures, SVG rendering |97| `textures-and-ticker.md` | Texture system, Ticker/frame timing |98| `renderers.md` | WebGL/WebGPU/Canvas renderer selection, CacheAsTexture |99| `application.md` | Application class, resize plugin, ticker plugin |100| `advanced-integration.md` | Mixing PixiJS + Three.js |101102### API lookup103104`api-reference-toc.md` → index of 93 exported classes with line numbers →105`api-reference.md` (22K lines, use `view` with line ranges).106107## Critical v8 differences108109These patterns changed from v7. Claude's training data likely reflects v7 and110will generate wrong code without these.111112### Initialization & structure113114- **Async init**: `await app.init(options)` — not constructor options115- **`app.canvas`** not `app.view`116- **All constructors use options objects**: `new BlurFilter({ blur: 8 })` not117 `new BlurFilter(8)`. Applies to Text, BitmapText, HTMLText, Mesh, MeshPlane,118 NineSliceSprite, TilingSprite, DisplacementFilter, etc.119- **Single package**: `import { X } from 'pixi.js'` — no more `@pixi/sprite`,120 `@pixi/app` etc.121- **`skipExtensionImports`**: tree-shaking via selective `import 'pixi.js/app'`122 etc.123124### Scene graph125126- **Leaf nodes cannot have children**: Sprite, Graphics, Mesh, Text,127 TilingSprite, HTMLText cannot `addChild()`. Only Container can. Wrap in128 Container if nesting needed.129- **`container.label`** replaces `container.name`130- **`container.origin`**: new property, CSS-like transform-origin (distinct from131 pivot — origin doesn't shift position)132- **`onRender` replaces `updateTransform`**:133 `container.onRender = () => { ... }` — the old override pattern no longer runs134 per frame135- **`getBounds()` returns `Bounds`**, not `Rectangle` — use136 `container.getBounds().rectangle`137- **`reparentChild()`**: preserves world transform when moving between138 containers139140### Rendering & display141142- **Graphics**: chained `.rect().fill().stroke()` — no `beginFill`/`endFill`143- **GraphicsContext**: reusable shared geometry — `new Graphics(context)` where144 multiple Graphics share one context145- **RenderLayer**: new class for rendering children above filtered containers146 (`layer.attach(child)`)147- **DOMContainer**: new class for embedding HTML elements in the scene graph148 with transforms149- **CullerPlugin**: opt-in — `extensions.add(CullerPlugin)`. Set150 `cullable = true` on containers.151- **`cacheAsTexture()`** replaces `cacheAsBitmap`. Call `updateCacheTexture()`152 after changes.153154### Text155156- **Text**: `new Text({ text, style })` options object — not positional args157- **Tagged text**: `style.tagStyles` for inline `<bold>` style tags (new in158 v8.16)159- **SplitText/SplitBitmapText**: per-character/word/line animation containers160 (experimental)161- **GifSprite**: new, from `import { GifSprite } from 'pixi.js/gif'`162163### Assets & textures164165- **Assets**: `await Assets.load()` — `PIXI.Loader` removed entirely166- **`Assets.add({ alias, src })`** — object form, not positional args167- **Textures must be loaded before use** — `Texture.from()` only works for168 already-loaded assets. For images from API routes (no file extension), load169 via `HTMLImageElement` then `Texture.from(img)`.170- **`SCALE_MODES` → strings**: `'nearest'`, `'linear'` (not171 `SCALE_MODES.NEAREST`)172- **`WRAP_MODES` → strings**: `'repeat'`, `'clamp-to-edge'`, `'mirror-repeat'`173174### Events & interaction175176- **Default `eventMode` is `passive`** — must set `'static'` or `'dynamic'` for177 interactive objects178- **Ticker callback passes `Ticker` instance**, not delta — use179 `ticker.deltaTime`, `ticker.elapsedMS`180181### Masks & filters182183- **Masks**: `setMask({ mask, inverse })` for inverse masks184- **Filters**: `filter.antialias` replaces `filter.resolution`185- **Custom filters**:186 `new Filter({ glProgram: GlProgram.from({...}), resources: {...} })` —187 textures are resources, not uniforms188- **Community filters**: `import { X } from 'pixi-filters/x'` not189 `@pixi/filter-x`190191### Particles192193- **ParticleContainer uses `Particle`, not `Sprite`**:194 `addParticle()`/`removeParticle()` — particles have195 `scaleX`/`scaleY`/`anchorX`/`anchorY`/`color`, not the full Container API.196 Stored in `particleChildren`, not `children`.197198### Misc199200- **`utils` removed** — direct imports: `import { isMobile } from 'pixi.js'`201- **`settings` removed** — use `AbstractRenderer.defaultOptions` or pass to init202203## Design decisions for PixiJS + SvelteKit204205### Architecture: plugins as pure TS classes206207Keep PixiJS logic in **pure TypeScript classes** (plugins) with no Svelte or DOM208imports. The Svelte component is a thin wrapper that creates the Application,209instantiates plugins, and exposes reactive state via `$bindable` props.210211```212PixiCanvas.svelte — thin Svelte wrapper (onMount, props, context)213 ├── ImagePlugin.ts — loads images, zoom/pan, viewport transforms214 ├── ArrowDataPlugin.ts — Arrow Table → batched Graphics rendering215 └── AnnotationPlugin.ts — drawing state machine, hit testing216```217218This pattern makes plugins:219220- **Testable** with Vitest without a browser221- **Reusable** across different Svelte components222- **Publishable** as a standalone library223224### SSR safety225226PixiJS requires DOM + WebGL/WebGPU. Use `onMount` (never runs on server) with a227`browser` guard, or `export const ssr = false` on the route.228229### Performance scaling230231| Object count | Approach |232| ------------ | ------------------------------------------------------------------------------------------ |233| < 100 | Individual Graphics objects per item |234| 100–5000 | Batch by category (e.g. one Graphics per status color = 5 draw calls for 5000 annotations) |235| 5000+ | Add viewport culling, `isRenderGroup`, `cacheAsTexture` |236| 10000+ | `ParticleContainer` with `Particle` instances |237238Additional levers: `isRenderGroup` (GPU container transforms), `cacheAsTexture`239(flatten static subtrees), `cullable`, `RenderLayer` (UI above filters), object240pooling, `GraphicsContext` (shared geometry).241242### Viewport pan/zoom pattern243244Use a Container with `isRenderGroup: true` as the viewport. Apply scale/position245transforms to the stage or viewport container. Handle wheel zoom centered on246cursor position.247248```ts249// Zoom toward cursor250const factor = e.deltaY > 0 ? 1 / 1.1 : 1.1;251const newZoom = Math.max(0.05, Math.min(50, zoom * factor));252panX = mouseX - (mouseX - panX) * (newZoom / zoom);253panY = mouseY - (mouseY - panY) * (newZoom / zoom);254zoom = newZoom;255stage.scale.set(zoom);256stage.position.set(panX, panY);257```258259### Loading images from API routes (no file extension)260261`Assets.load()` uses URL extension to detect format. API routes like262`/api/images/page-001` have no extension. Load via HTMLImageElement instead:263264```ts265const img = new Image();266img.crossOrigin = "anonymous";267img.src = url;268await new Promise((resolve, reject) => {269 img.onload = resolve;270 img.onerror = reject;271});272const texture = Texture.from(img);273```274275### Graphics batching (Rerun-inspired columnar rendering)276277For data-driven rendering (annotations, overlays), batch shapes by visual278property (e.g. color) into a single Graphics object per group. This reduces GPU279draw calls from N to the number of groups.280281```ts282// 5 draw calls instead of 5000283for (const [color, rowIndices] of colorGroups) {284 const g = graphicsMap.get(color);285 g.clear();286 for (const i of rowIndices) {287 g.rect(xCol[i], yCol[i], wCol[i], hCol[i]);288 }289 g.fill({ color, alpha: 0.15 });290 g.stroke({ color, width: 2 });291}292```293294## Routing295296| Task | Read |297| ------------------------------ | --------------------------------------------------------------------------- |298| Set up pixi in SvelteKit | This file (integration pattern above) |299| Click/drag/pointer events | `examples.md` → Events |300| Draw shapes/paths/curves | `examples.md` → Graphics API, then `container-and-graphics.md` |301| Filters or custom shaders | `examples.md` → Filters / Mesh & Shaders, then `interaction-and-effects.md` |302| Text/labels/fonts | `examples.md` → Text, then `text.md` |303| Load textures/spritesheets | `assets.md`, then `examples.md` → Asset Loading |304| Responsive layout / flexbox | `ecosystem.md` → @pixi/layout |305| Buttons / sliders / UI widgets | `ecosystem.md` → @pixi/ui |306| Responsive resize / scaling | `ecosystem.md` → Responsive Patterns |307| Pan / zoom / viewport | `ecosystem.md` → Viewport Patterns |308| Custom extensions | `ecosystem.md` → Extensions System |309| Slow rendering / optimization | `core-concepts.md` → Performance Tips |310| 10k+ objects | `core-concepts.md` → Performance Tips + ParticleContainer |311| UI above filtered content | `examples.md` → RenderLayer |312| Declarative scene (simple) | `svelte-pixi.md` (optional, for simple component-tree scenes only) |313| Specific class API | `api-reference-toc.md` → line range in `api-reference.md` |