PixiJS is the fastest 2D WebGL renderer for the web — new Application() creates a renderer with automatic WebGL/WebGPU detection. app.stage is the root Container. Sprite.from(url) loads and displays a texture. new Graphics() draws rectangles, circles, and paths procedurally. new Text("hello", style) renders text with web fonts. app.ticker.add(delta => update(delta)) runs the game loop at 60fps. Assets.load(manifest) handles async asset loading with progress. sprite.on("pointerdown", handler) enables interaction — set sprite.interactive = true. ParticleContainer renders thousands of sprites efficiently. ColorMatrixFilter and BlurFilter apply post-process effects. RenderTexture.create({ width, height }) renders to an off-screen surface. @pixi/react provides <Application>, <Sprite>, <Container>, and useTick for React integration. Claude Code generates PixiJS game loops, particle effects, interactive dashboards, sprite animation systems, and map overlays.
CLAUDE.md for PixiJS
## PixiJS Stack
- Version: pixi.js >= 8.4, @pixi/react >= 8.0
- App: const app = new Application(); await app.init({ width, height, background: 0x1a1a2e, resizeTo: window })
- Sprite: const sprite = Sprite.from("/assets/ship.png"); sprite.anchor.set(0.5); app.stage.addChild(sprite)
- Graphics: const g = new Graphics(); g.rect(x, y, w, h).fill(0x3b82f6).stroke({ color: 0xffffff, width: 2 })
- Ticker: app.ticker.add((ticker) => { sprite.rotation += 0.01 * ticker.deltaTime })
- Events: sprite.interactive = true; sprite.on("pointerdown", handler)
- Assets: await Assets.load([{ alias: "ship", src: "/ship.png" }])
- React: <Application width={800} height={600}><Sprite texture={texture} x={400} y={300} /></Application>
PixiJS Application Setup
// lib/pixi/app.ts — PixiJS application with resize and cleanup
import { Application, Assets, Container, Ticker } from "pixi.js"
export async function createPixiApp(canvas: HTMLCanvasElement): Promise<Application> {
const app = new Application()
await app.init({
canvas,
background: 0x0f172a,
antialias: true,
autoDensity: true, // Retina/HiDPI support
resolution: window.devicePixelRatio || 1,
resizeTo: canvas.parentElement ?? window,
powerPreference: "high-performance",
})
return app
}
// Preload all game assets
export async function loadAssets(
onProgress?: (progress: number) => void,
): Promise<void> {
Assets.addBundle("game", [
{ alias: "ship", src: "/assets/ship.png" },
{ alias: "asteroid", src: "/assets/asteroid.png" },
{ alias: "explosion", src: "/assets/explosion-sheet.json" },
{ alias: "starfield", src: "/assets/starfield.png" },
])
await Assets.loadBundle("game", (progress) => {
onProgress?.(Math.round(progress * 100))
})
}
Sprite Animation System
// lib/pixi/SpriteAnimator.ts — animated sprite with state machine
import { AnimatedSprite, Assets, Texture, Container } from "pixi.js"
type AnimationState = "idle" | "walk" | "run" | "attack" | "death"
const FRAME_COUNTS: Record<AnimationState, number> = {
idle: 8,
walk: 12,
run: 8,
attack: 6,
death: 10,
}
const ANIMATION_SPEEDS: Record<AnimationState, number> = {
idle: 0.12,
walk: 0.18,
run: 0.25,
attack: 0.22,
death: 0.15,
}
export class CharacterSprite extends Container {
private sprite: AnimatedSprite
private state: AnimationState = "idle"
private onDeathComplete?: () => void
constructor() {
super()
// Build textures from sprite sheet
const idleTextures = Array.from({ length: FRAME_COUNTS.idle }, (_, i) =>
Texture.from(`idle_${String(i).padStart(2, "0")}.png`),
)
this.sprite = new AnimatedSprite(idleTextures)
this.sprite.anchor.set(0.5, 1) // Feet anchor
this.sprite.animationSpeed = ANIMATION_SPEEDS.idle
this.sprite.play()
this.addChild(this.sprite)
}
setState(next: AnimationState, onComplete?: () => void): void {
if (this.state === next) return
this.state = next
const frameCount = FRAME_COUNTS[next]
const textures = Array.from({ length: frameCount }, (_, i) =>
Texture.from(`${next}_${String(i).padStart(2, "0")}.png`),
)
this.sprite.textures = textures
this.sprite.animationSpeed = ANIMATION_SPEEDS[next]
this.sprite.loop = next !== "death" && next !== "attack"
this.sprite.gotoAndPlay(0)
if (next === "death") {
this.sprite.onComplete = () => onComplete?.()
} else {
this.sprite.onComplete = undefined
}
}
get currentState(): AnimationState {
return this.state
}
}
Particle System
// lib/pixi/ParticleSystem.ts — high-performance particles with ParticleContainer
import { ParticleContainer, Sprite, Ticker, Assets } from "pixi.js"
interface Particle {
sprite: Sprite
vx: number
vy: number
gravity: number
life: number
maxLife: number
startScale: number
endScale: number
}
export class ParticleSystem {
container: ParticleContainer
private particles: Particle[] = []
private pool: Sprite[] = []
constructor(maxParticles = 2000) {
this.container = new ParticleContainer(maxParticles, {
position: true,
alpha: true,
scale: true,
rotation: true,
})
}
emit(x: number, y: number, count = 20, config?: Partial<{
spread: number
speed: number
gravity: number
life: number
startScale: number
endScale: number
}>): void {
const {
spread = Math.PI * 2,
speed = 4,
gravity = 0.15,
life = 60,
startScale = 0.8,
endScale = 0.1,
} = config ?? {}
for (let i = 0; i < count; i++) {
const sprite = this.pool.pop() ?? Sprite.from("particle")
sprite.anchor.set(0.5)
sprite.tint = 0xff6b35
const angle = (Math.random() - 0.5) * spread
const v = speed * (0.5 + Math.random() * 0.5)
this.container.addChild(sprite)
this.particles.push({
sprite,
vx: Math.sin(angle) * v,
vy: -Math.cos(angle) * v,
gravity,
life,
maxLife: life,
startScale,
endScale,
})
sprite.x = x
sprite.y = y
sprite.scale.set(startScale)
sprite.alpha = 1
}
}
update(ticker: Ticker): void {
const delta = ticker.deltaTime
for (let i = this.particles.length - 1; i >= 0; i--) {
const p = this.particles[i]
p.vx *= 0.98
p.vy += p.gravity * delta
p.sprite.x += p.vx * delta
p.sprite.y += p.vy * delta
p.life -= delta
const progress = 1 - p.life / p.maxLife
p.sprite.alpha = 1 - progress
p.sprite.scale.set(p.startScale + (p.endScale - p.startScale) * progress)
if (p.life <= 0) {
this.container.removeChild(p.sprite)
this.pool.push(p.sprite)
this.particles.splice(i, 1)
}
}
}
}
React Integration
// components/pixi/StarfieldBackground.tsx — @pixi/react integration
"use client"
import { Application, extend } from "@pixi/react"
import { Container, TilingSprite, Graphics, Ticker as PixiTicker } from "pixi.js"
import { useCallback, useRef } from "react"
// Register components with @pixi/react
extend({ Container, TilingSprite, Graphics })
interface StarfieldProps {
width: number
height: number
}
export function StarfieldBackground({ width, height }: StarfieldProps) {
const offsetRef = useRef({ x: 0, y: 0 })
const drawStars = useCallback((g: InstanceType<typeof Graphics>) => {
g.clear()
// Static star field — drawn once
for (let i = 0; i < 200; i++) {
const x = Math.random() * width
const y = Math.random() * height
const size = Math.random() * 1.5 + 0.5
const alpha = Math.random() * 0.6 + 0.4
const color = Math.random() > 0.9 ? 0xffd700 : 0xffffff
g.circle(x, y, size).fill({ color, alpha })
}
}, [width, height])
return (
<Application width={width} height={height} background={0x020617}>
<pixiGraphics draw={drawStars} />
</Application>
)
}
Interactive Data Visualization
// lib/pixi/BubbleChart.ts — interactive PixiJS bubble chart
import { Application, Container, Graphics, Text, TextStyle, FederatedPointerEvent } from "pixi.js"
interface DataPoint {
label: string
value: number
color: number
x: number
y: number
}
export function createBubbleChart(app: Application, data: DataPoint[]): Container {
const chart = new Container()
const maxValue = Math.max(...data.map(d => d.value))
data.forEach(point => {
const radius = 20 + (point.value / maxValue) * 60
const group = new Container()
group.position.set(point.x, point.y)
group.interactive = true
group.cursor = "pointer"
// Bubble
const bubble = new Graphics()
bubble.circle(0, 0, radius)
bubble.fill({ color: point.color, alpha: 0.7 })
bubble.stroke({ color: point.color, width: 2, alpha: 1 })
const labelStyle = new TextStyle({
fontSize: Math.max(11, Math.min(16, radius * 0.35)),
fill: 0xffffff,
fontFamily: "Inter, sans-serif",
align: "center",
})
const label = new Text({ text: point.label, style: labelStyle })
label.anchor.set(0.5)
group.addChild(bubble, label)
// Hover effect
group.on("pointerenter", () => {
bubble.clear()
bubble.circle(0, 0, radius + 4)
bubble.fill({ color: point.color, alpha: 0.9 })
bubble.stroke({ color: 0xffffff, width: 2 })
})
group.on("pointerleave", () => {
bubble.clear()
bubble.circle(0, 0, radius)
bubble.fill({ color: point.color, alpha: 0.7 })
bubble.stroke({ color: point.color, width: 2, alpha: 1 })
})
chart.addChild(group)
})
app.stage.addChild(chart)
return chart
}
For the Konva alternative when a React-friendly 2D canvas library with built-in drag-and-drop, Transformer selection handles, and simpler state management for document editor use cases is preferred — Konva’s react-konva integration is more idiomatic for React developers, while PixiJS is better for games and high-performance rendering with thousands of objects, see the Konva guide. For the Three.js/React Three Fiber alternative when 3D WebGL rendering with geometry, lighting, physics, and PBR materials is needed — PixiJS only does 2D while Three.js handles the full 3D scene graph with cameras, shaders, and post-processing, see the React Three Fiber guide. The Claude Skills 360 bundle includes PixiJS skill sets covering game loops, particle systems, and interactive graphics. Start with the free tier to try 2D WebGL generation.