[TERMINAL · SKILLS]
> mounting /skills...
> indexing skill manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Skills/PixiJS
>

PixiJS

Renders fast 2D graphics in the browser with PixiJS 8, a WebGL and WebGPU engine for sprites, text, filters, masks and particles. Use when a user asks to build a 2D game, an interactive visualization, an animated banner, a sprite animation from a spritesheet, a particle effect, or to speed up a canvas scene with thousands of objects.

#2d-rendering#webgl#graphics#animation#game-development
terminal-skillsv1.1.0
Works with:claude-codeopenai-codexgemini-clicursor
Source
Trust Score
87/ 100
1.14×
Impact

Validation

Score is stale (SKILL.md changed since last review)
Quality
87/ 100
Does it follow best practices?
4 PASS · 2 WEAK
Security
Passed
No known issues
Content review + injection scan
Impact
1.14×
71% → 81% agent success
Avg across 3 eval scenarios
Scored 10/3/2026 · skill v1.1.0
$
✓ Installed pixijs v1.1.0

Getting Started

  1. Install the skill using the command above
  2. Open your AI coding agent (Claude Code, Codex, Gemini CLI, or Cursor)
  3. Reference the skill in your prompt
  4. The AI will use the skill's capabilities automatically

Example Prompts

  • "Review the open pull requests and summarize what needs attention"
  • "Generate a changelog from the last 20 commits on the main branch"

Documentation

Overview

PixiJS is a 2D rendering library for the web. It draws a scene graph of sprites, text, vector graphics and meshes on the GPU with WebGL (default) or WebGPU, batches draw calls automatically, and adds an asset loader, a ticker, a pointer-event system and filters. It is a renderer, not a full game engine: physics, audio and game logic come from other libraries.

This skill targets PixiJS 8 (current: 8.22). The v8 API differs from v7: the application is created with new Application() then await app.init(...), shapes use rect().fill(), and ParticleContainer holds lightweight Particle objects. There is no 2D-canvas fallback renderer; the CanvasRenderer is listed as "coming soon", so a device needs WebGL or WebGPU.

Instructions

Install and set up

bash
npm create pixi.js@latest      # scaffold a project (Vite template recommended)
npm install pixi.js            # or add to an existing project
typescript
// src/main.ts
import { Application, Assets, Sprite } from 'pixi.js'

(async () => {                  // wrap in a function: top-level await breaks some Vite production builds
  const app = new Application()
  await app.init({
    background: '#1a1a2e',
    resizeTo: window,
    antialias: false,                         // crisp pixel art
    resolution: window.devicePixelRatio,
    autoDensity: true,                        // keeps CSS size correct on retina screens
  })
  document.body.appendChild(app.canvas)       // v8: app.canvas, not app.view

  await Assets.load([
    { alias: 'hero', src: '/sprites/hero.png' },
    { alias: 'tileset', src: '/sprites/tileset.png' },
  ])

  const hero = Sprite.from('hero')            // works for an alias that is already loaded
  hero.anchor.set(0.5)
  hero.position.set(app.screen.width / 2, app.screen.height / 2)
  app.stage.addChild(hero)

  app.ticker.add((ticker) => {
    hero.rotation += 0.01 * ticker.deltaTime  // v8 passes the Ticker, not a number
  })
})()

preference: 'webgpu' in init asks for the WebGPU renderer; the docs call it experimental and recommend WebGL for production.

Containers and layers

typescript
import { Container } from 'pixi.js'

const world = new Container()
const effects = new Container()
const hud = new Container()
app.stage.addChild(world, effects, hud)       // later children draw on top

world.sortableChildren = true                 // depth sort by zIndex
app.ticker.add(() => enemies.forEach((e) => { e.zIndex = e.y }))

Filters

typescript
import { BlurFilter, ColorMatrixFilter, DisplacementFilter, Sprite } from 'pixi.js'

background.filters = [new BlurFilter({ strength: 4 })]
const grayscale = new ColorMatrixFilter()
grayscale.desaturate()
deadEnemy.filters = [grayscale]

const map = Sprite.from('displacement-map')
map.texture.source.style.addressMode = 'repeat'
waterLayer.filters = [new DisplacementFilter({ sprite: map, scale: 20 })]
app.stage.addChild(map)                        // the map sprite must be on the stage to be sampled

Release a filter's memory with container.filters = null. Blend filters such as HardMixBlend need import 'pixi.js/advanced-blend-modes'. More effects live in the separate pixi-filters package.

Spritesheet animation

typescript
import { AnimatedSprite, Assets } from 'pixi.js'

const sheet = await Assets.load('/sprites/hero.json')   // TexturePacker or AssetPack JSON
const walk = new AnimatedSprite(sheet.animations['walk'])
walk.animationSpeed = 0.15
walk.play()
app.stage.addChild(walk)

walk.textures = sheet.animations['attack']               // switch clips
walk.play()

Text and Graphics

typescript
import { Assets, FillGradient, Graphics, Text } from 'pixi.js'

await Assets.load({ src: '/fonts/press-start-2p.woff2', data: { family: 'Press Start 2P' } })

const scoreText = new Text({
  text: 'Score: 0',
  style: {
    fontFamily: 'Press Start 2P',
    fontSize: 24,
    fill: new FillGradient({ type: 'linear', colorStops: [{ offset: 0, color: '#ffffff' }, { offset: 1, color: '#00ff88' }] }),
    stroke: { color: '#000000', width: 4 },
    dropShadow: { color: '#000000', distance: 2 },
  },
})

const healthBar = new Graphics()
healthBar.rect(0, 0, 200, 20).fill(0x333333)
healthBar.rect(2, 2, 196 * hp, 16).fill(0x00ff00)   // v8: shape first, then fill()/stroke()

Gradients are FillGradient objects in v8; an array of colours as fill was v7 syntax. Changing text.text re-rasterizes the text, so for a score that changes every frame use BitmapText.

Many objects: ParticleContainer

typescript
import { Particle, ParticleContainer, Texture } from 'pixi.js'

const sparks = new ParticleContainer({ dynamicProperties: { position: true, rotation: true, vertex: false, color: false } })
for (let i = 0; i < 50_000; i++) {
  sparks.addParticle(new Particle({ texture: Texture.from('particle'), x: Math.random() * 800, y: Math.random() * 600 }))
}
app.stage.addChild(sparks)

Particles have no children, events or filters; use addParticle/removeParticle instead of addChild. Properties not listed as dynamic are uploaded only when you call sparks.update(). The API is marked experimental.

Examples

Example 1: Animated hero in a platformer

User prompt: "Load hero.json and make the character walk, then switch to attack when I press space."

Load the sheet with Assets.load('/sprites/hero.json'), create an AnimatedSprite from sheet.animations['walk'], call play(), and on keydown for Space set textures to sheet.animations['attack'], loop = false, then play() and restore walk in onComplete. Result: the sprite loops the walk cycle and plays a single attack animation on each key press.

Example 2: Rain of 100,000 particles

User prompt: "My falling-snow effect with 20,000 Sprites runs at 20 fps. Make it smooth."

Replace the Sprite objects with Particle objects in one ParticleContainer with dynamicProperties: { position: true }, and in the ticker update each particle.y += speed * ticker.deltaTime, wrapping at the bottom. Result: one batched draw call; 100,000 flakes stay at the display refresh rate on a typical laptop.

Guidelines

  • Always await app.init() before touching app.canvas, app.stage or app.screen.
  • Keep layers in separate containers; draw order inside a container is child order unless sortableChildren is on.
  • Spritesheets batch best: sprites that share a texture atlas draw together, and mixing sprite, graphics and text objects alternately breaks batches.
  • Filters, masks and blend modes cost render passes. Use few, and prefer rectangle masks (scissor) over sprite masks.
  • Textures are garbage-collected after about 3600 idle frames; call destroy() on objects you remove. sprite.destroy(true) also destroys the texture, which breaks other sprites that share it, so use sprite.destroy() for shared textures and Assets.unload(alias) to free an asset.
  • Pool objects that appear and disappear often (bullets, particles) instead of creating them every frame.
  • For a plain static page or a simple chart, SVG or Canvas 2D is lighter than PixiJS.
  • Using React? Use the @pixi/react package instead of mixing imperative setup into components.