pixijs-2d
npx skills add https://github.com/freshtechbro/claudedesignskills --skill pixijs-2d
Agent 安装分布
Skill 文档
PixiJS 2D Rendering Skill
Fast, lightweight 2D rendering engine for creating interactive graphics, particle effects, and canvas-based applications using WebGL/WebGPU.
When to Use This Skill
Trigger this skill when you encounter:
- “Create 2D particle effects” or “animated particles”
- “2D sprite animation” or “sprite sheet handling”
- “Interactive canvas graphics” or “2D game”
- “UI overlays on 3D scenes” or “HUD layer”
- “Draw shapes programmatically” or “vector graphics API”
- “Optimize rendering performance” or “thousands of sprites”
- “Apply visual filters” or “blur/displacement effects”
- “Lightweight 2D engine” or “alternative to Canvas2D”
Use PixiJS for: High-performance 2D rendering (up to 100,000+ sprites), particle systems, interactive UI, 2D games, data visualization with WebGL acceleration.
Don’t use for: 3D graphics (use Three.js/R3F), simple animations (use Motion/GSAP), basic DOM manipulation.
Core Concepts
1. Application & Renderer
The entry point for PixiJS applications:
import { Application } from 'pixi.js';
const app = new Application();
await app.init({
width: 800,
height: 600,
backgroundColor: 0x1099bb,
antialias: true, // Smooth edges
resolution: window.devicePixelRatio || 1
});
document.body.appendChild(app.canvas);
Key Properties:
app.stage: Root container for all display objectsapp.renderer: WebGL/WebGPU renderer instanceapp.ticker: Update loop for animationsapp.screen: Canvas dimensions
2. Sprites & Textures
Core visual elements loaded from images:
import { Assets, Sprite } from 'pixi.js';
// Load texture
const texture = await Assets.load('path/to/image.png');
// Create sprite
const sprite = new Sprite(texture);
sprite.anchor.set(0.5); // Center pivot
sprite.position.set(400, 300);
sprite.scale.set(2); // 2x scale
sprite.rotation = Math.PI / 4; // 45 degrees
sprite.alpha = 0.8; // 80% opacity
sprite.tint = 0xff0000; // Red tint
app.stage.addChild(sprite);
Quick Creation:
const sprite = Sprite.from('path/to/image.png');
3. Graphics API
Draw vector shapes programmatically:
import { Graphics } from 'pixi.js';
const graphics = new Graphics();
// Rectangle
graphics.rect(50, 50, 100, 100).fill('blue');
// Circle with stroke
graphics.circle(200, 100, 50).fill('red').stroke({ width: 2, color: 'white' });
// Complex path
graphics
.moveTo(300, 100)
.lineTo(350, 150)
.lineTo(250, 150)
.closePath()
.fill({ color: 0x00ff00, alpha: 0.5 });
app.stage.addChild(graphics);
SVG Support:
graphics.svg('<svg><path d="M 100 350 q 150 -300 300 0" /></svg>');
4. ParticleContainer
Optimized container for rendering thousands of sprites:
import { ParticleContainer, Particle, Texture } from 'pixi.js';
const texture = Texture.from('particle.png');
const container = new ParticleContainer({
dynamicProperties: {
position: true, // Allow position updates
scale: false, // Static scale
rotation: false, // Static rotation
color: false // Static color
}
});
// Add 10,000 particles
for (let i = 0; i < 10000; i++) {
const particle = new Particle({
texture,
x: Math.random() * 800,
y: Math.random() * 600
});
container.addParticle(particle);
}
app.stage.addChild(container);
Performance: Up to 10x faster than regular Container for static properties.
5. Filters
Apply per-pixel effects using WebGL shaders:
import { BlurFilter, DisplacementFilter, ColorMatrixFilter } from 'pixi.js';
// Blur
const blurFilter = new BlurFilter({ strength: 8, quality: 4 });
sprite.filters = [blurFilter];
// Multiple filters
sprite.filters = [
new BlurFilter({ strength: 4 }),
new ColorMatrixFilter() // Color transforms
];
// Custom filter area for performance
sprite.filterArea = new Rectangle(0, 0, 200, 100);
Available Filters:
BlurFilter: Gaussian blurColorMatrixFilter: Color transformations (sepia, grayscale, etc.)DisplacementFilter: Warp/distort pixelsAlphaFilter: Flatten alpha across childrenNoiseFilter: Random grain effectFXAAFilter: Anti-aliasing
6. Text Rendering
Display text with styling:
import { Text, BitmapText, TextStyle } from 'pixi.js';
// Standard Text
const style = new TextStyle({
fontFamily: 'Arial',
fontSize: 36,
fill: '#ffffff',
stroke: { color: '#000000', width: 4 },
filters: [new BlurFilter()] // Bake filter into texture
});
const text = new Text({ text: 'Hello PixiJS!', style });
text.position.set(100, 100);
// BitmapText (faster for dynamic text)
const bitmapText = new BitmapText({
text: 'Score: 0',
style: { fontFamily: 'MyBitmapFont', fontSize: 24 }
});
Performance Tip: Use BitmapText for frequently changing text (scores, counters).
Common Patterns
Pattern 1: Basic Interactive Sprite
import { Application, Assets, Sprite } from 'pixi.js';
const app = new Application();
await app.init({ width: 800, height: 600 });
document.body.appendChild(app.canvas);
const texture = await Assets.load('bunny.png');
const bunny = new Sprite(texture);
bunny.anchor.set(0.5);
bunny.position.set(400, 300);
bunny.eventMode = 'static'; // Enable interactivity
bunny.cursor = 'pointer';
// Events
bunny.on('pointerdown', () => {
bunny.scale.set(1.2);
});
bunny.on('pointerup', () => {
bunny.scale.set(1.0);
});
bunny.on('pointerover', () => {
bunny.tint = 0xff0000; // Red on hover
});
bunny.on('pointerout', () => {
bunny.tint = 0xffffff; // Reset
});
app.stage.addChild(bunny);
// Animation loop
app.ticker.add((ticker) => {
bunny.rotation += 0.01 * ticker.deltaTime;
});
Pattern 2: Drawing with Graphics
import { Graphics, Application } from 'pixi.js';
const app = new Application();
await app.init({ width: 800, height: 600 });
document.body.appendChild(app.canvas);
const graphics = new Graphics();
// Rectangle with gradient
graphics.rect(50, 50, 200, 100).fill({
color: 0x3399ff,
alpha: 0.8
});
// Circle with stroke
graphics.circle(400, 300, 80)
.fill('yellow')
.stroke({ width: 4, color: 'orange' });
// Star shape
graphics.star(600, 300, 5, 50, 0).fill({ color: 0xffdf00, alpha: 0.9 });
// Custom path
graphics
.moveTo(100, 400)
.bezierCurveTo(150, 300, 250, 300, 300, 400)
.stroke({ width: 3, color: 'white' });
// Holes
graphics
.rect(450, 400, 150, 100).fill('red')
.beginHole()
.circle(525, 450, 30)
.endHole();
app.stage.addChild(graphics);
// Dynamic drawing (animation)
app.ticker.add(() => {
graphics.clear();
const time = Date.now() * 0.001;
const x = 400 + Math.cos(time) * 100;
const y = 300 + Math.sin(time) * 100;
graphics.circle(x, y, 20).fill('cyan');
});
Pattern 3: Particle System with ParticleContainer
import { Application, ParticleContainer, Particle, Texture } from 'pixi.js';
const app = new Application();
await app.init({ width: 800, height: 600, backgroundColor: 0x000000 });
document.body.appendChild(app.canvas);
const texture = Texture.from('spark.png');
const particles = new ParticleContainer({
dynamicProperties: {
position: true, // Update positions every frame
scale: true, // Fade out by scaling
rotation: true, // Rotate particles
color: false // Static color
}
});
const particleData = [];
// Create particles
for (let i = 0; i < 5000; i++) {
const particle = new Particle({
texture,
x: 400,
y: 300,
scaleX: 0.5,
scaleY: 0.5
});
particles.addParticle(particle);
particleData.push({
particle,
vx: (Math.random() - 0.5) * 5,
vy: (Math.random() - 0.5) * 5 - 2, // Slight upward bias
life: 1.0
});
}
app.stage.addChild(particles);
// Update loop
app.ticker.add((ticker) => {
particleData.forEach(data => {
// Physics
data.particle.x += data.vx * ticker.deltaTime;
data.particle.y += data.vy * ticker.deltaTime;
data.vy += 0.1 * ticker.deltaTime; // Gravity
// Fade out
data.life -= 0.01 * ticker.deltaTime;
data.particle.scaleX = data.life * 0.5;
data.particle.scaleY = data.life * 0.5;
// Reset particle
if (data.life <= 0) {
data.particle.x = 400;
data.particle.y = 300;
data.vx = (Math.random() - 0.5) * 5;
data.vy = (Math.random() - 0.5) * 5 - 2;
data.life = 1.0;
}
});
});
Pattern 4: Applying Filters
import { Application, Sprite, Assets, BlurFilter, DisplacementFilter } from 'pixi.js';
const app = new Application();
await app.init({ width: 800, height: 600 });
document.body.appendChild(app.canvas);
const texture = await Assets.load('photo.jpg');
const photo = new Sprite(texture);
photo.position.set(100, 100);
// Blur filter
const blurFilter = new BlurFilter({ strength: 5, quality: 4 });
// Displacement filter (wavy effect)
const displacementTexture = await Assets.load('displacement.jpg');
const displacementSprite = Sprite.from(displacementTexture);
const displacementFilter = new DisplacementFilter({
sprite: displacementSprite,
scale: 50
});
// Apply multiple filters
photo.filters = [blurFilter, displacementFilter];
// Optimize with filterArea
photo.filterArea = new Rectangle(0, 0, photo.width, photo.height);
app.stage.addChild(photo);
// Animate displacement
app.ticker.add((ticker) => {
displacementSprite.x += 1 * ticker.deltaTime;
displacementSprite.y += 0.5 * ticker.deltaTime;
});
Pattern 5: Custom Filter with Shaders
import { Filter, GlProgram } from 'pixi.js';
const vertex = `
in vec2 aPosition;
out vec2 vTextureCoord;
uniform vec4 uInputSize;
uniform vec4 uOutputFrame;
uniform vec4 uOutputTexture;
vec4 filterVertexPosition() {
vec2 position = aPosition * uOutputFrame.zw + uOutputFrame.xy;
position.x = position.x * (2.0 / uOutputTexture.x) - 1.0;
position.y = position.y * (2.0*uOutputTexture.z / uOutputTexture.y) - uOutputTexture.z;
return vec4(position, 0.0, 1.0);
}
vec2 filterTextureCoord() {
return aPosition * (uOutputFrame.zw * uInputSize.zw);
}
void main() {
gl_Position = filterVertexPosition();
vTextureCoord = filterTextureCoord();
}
`;
const fragment = `
in vec2 vTextureCoord;
uniform sampler2D uTexture;
uniform float uTime;
void main() {
vec2 uv = vTextureCoord;
// Wave distortion
float wave = sin(uv.y * 10.0 + uTime) * 0.05;
vec4 color = texture(uTexture, vec2(uv.x + wave, uv.y));
gl_FragColor = color;
}
`;
const customFilter = new Filter({
glProgram: new GlProgram({ fragment, vertex }),
resources: {
timeUniforms: {
uTime: { value: 0.0, type: 'f32' }
}
}
});
sprite.filters = [customFilter];
// Update uniform
app.ticker.add((ticker) => {
customFilter.resources.timeUniforms.uniforms.uTime += 0.04 * ticker.deltaTime;
});
Pattern 6: Sprite Sheet Animation
import { Application, Assets, AnimatedSprite } from 'pixi.js';
const app = new Application();
await app.init({ width: 800, height: 600 });
document.body.appendChild(app.canvas);
// Load sprite sheet
await Assets.load('spritesheet.json');
// Create animation from frames
const frames = [];
for (let i = 0; i < 10; i++) {
frames.push(Texture.from(`frame_${i}.png`));
}
const animation = new AnimatedSprite(frames);
animation.anchor.set(0.5);
animation.position.set(400, 300);
animation.animationSpeed = 0.16; // ~10 FPS
animation.play();
app.stage.addChild(animation);
// Control playback
animation.stop();
animation.gotoAndPlay(0);
animation.onComplete = () => {
console.log('Animation completed!');
};
Pattern 7: Object Pooling for Performance
class SpritePool {
constructor(texture, initialSize = 100) {
this.texture = texture;
this.available = [];
this.active = [];
// Pre-create sprites
for (let i = 0; i < initialSize; i++) {
this.createSprite();
}
}
createSprite() {
const sprite = new Sprite(this.texture);
sprite.visible = false;
this.available.push(sprite);
return sprite;
}
spawn(x, y) {
let sprite = this.available.pop();
if (!sprite) {
sprite = this.createSprite();
}
sprite.position.set(x, y);
sprite.visible = true;
this.active.push(sprite);
return sprite;
}
despawn(sprite) {
sprite.visible = false;
const index = this.active.indexOf(sprite);
if (index > -1) {
this.active.splice(index, 1);
this.available.push(sprite);
}
}
reset() {
this.active.forEach(sprite => {
sprite.visible = false;
this.available.push(sprite);
});
this.active = [];
}
}
// Usage
const bulletTexture = Texture.from('bullet.png');
const bulletPool = new SpritePool(bulletTexture, 50);
// Spawn bullet
const bullet = bulletPool.spawn(100, 200);
app.stage.addChild(bullet);
// Despawn after 2 seconds
setTimeout(() => {
bulletPool.despawn(bullet);
}, 2000);
Integration Patterns
React Integration
import { useEffect, useRef } from 'react';
import { Application } from 'pixi.js';
function PixiCanvas() {
const canvasRef = useRef(null);
const appRef = useRef(null);
useEffect(() => {
const init = async () => {
const app = new Application();
await app.init({
width: 800,
height: 600,
backgroundColor: 0x1099bb
});
canvasRef.current.appendChild(app.canvas);
appRef.current = app;
// Setup scene
// ... add sprites, graphics, etc.
};
init();
return () => {
if (appRef.current) {
appRef.current.destroy(true, { children: true });
}
};
}, []);
return <div ref={canvasRef} />;
}
Three.js Overlay (2D UI on 3D)
import * as THREE from 'three';
import { Application, Sprite, Text } from 'pixi.js';
// Three.js scene
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight);
const renderer = new THREE.WebGLRenderer();
document.body.appendChild(renderer.domElement);
// PixiJS overlay
const pixiApp = new Application();
await pixiApp.init({
width: window.innerWidth,
height: window.innerHeight,
backgroundAlpha: 0 // Transparent background
});
pixiApp.canvas.style.position = 'absolute';
pixiApp.canvas.style.top = '0';
pixiApp.canvas.style.left = '0';
pixiApp.canvas.style.pointerEvents = 'none'; // Click through
document.body.appendChild(pixiApp.canvas);
// Add UI elements
const scoreText = new Text({ text: 'Score: 0', style: { fontSize: 24, fill: 'white' } });
scoreText.position.set(20, 20);
pixiApp.stage.addChild(scoreText);
// Render loop
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera); // 3D scene
pixiApp.renderer.render(pixiApp.stage); // 2D overlay
}
animate();
Performance Best Practices
1. Use ParticleContainer for Large Sprite Counts
// DON'T: Regular Container (slow for 1000+ sprites)
const container = new Container();
for (let i = 0; i < 10000; i++) {
container.addChild(new Sprite(texture));
}
// DO: ParticleContainer (10x faster)
const particles = new ParticleContainer({
dynamicProperties: { position: true }
});
for (let i = 0; i < 10000; i++) {
particles.addParticle(new Particle({ texture }));
}
2. Optimize Filter Usage
// Set filterArea to avoid runtime measurement
sprite.filterArea = new Rectangle(0, 0, 200, 100);
// Release filters when not needed
sprite.filters = null;
// Bake filters into Text at creation
const style = new TextStyle({
filters: [new BlurFilter()] // Applied once at texture creation
});
3. Manage Texture Memory
// Destroy textures when done
texture.destroy();
// Batch destruction with delays to prevent frame drops
textures.forEach((tex, i) => {
setTimeout(() => tex.destroy(), Math.random() * 100);
});
4. Enable Culling for Off-Screen Objects
sprite.cullable = true; // Skip rendering if outside viewport
// Use CullerPlugin
import { CullerPlugin } from 'pixi.js';
5. Cache Static Graphics as Bitmaps
// Convert complex graphics to texture for faster rendering
const complexShape = new Graphics();
// ... draw many shapes
complexShape.cacheAsBitmap = true; // Renders to texture once
6. Optimize Renderer Settings
const app = new Application();
await app.init({
antialias: false, // Disable on mobile for performance
resolution: 1, // Lower resolution on low-end devices
autoDensity: true
});
7. Use BitmapText for Dynamic Text
// DON'T: Standard Text (expensive updates)
const text = new Text({ text: `Score: ${score}` });
app.ticker.add(() => {
text.text = `Score: ${++score}`; // Re-renders texture each frame
});
// DO: BitmapText (much faster)
const bitmapText = new BitmapText({ text: `Score: ${score}` });
app.ticker.add(() => {
bitmapText.text = `Score: ${++score}`;
});
Common Pitfalls
Pitfall 1: Not Destroying Objects
Problem: Memory leaks from unreleased GPU resources.
Solution:
// Always destroy sprites and textures
sprite.destroy({ children: true, texture: true, baseTexture: true });
// Destroy filters
sprite.filters = null;
// Destroy graphics
graphics.destroy();
Pitfall 2: Updating Static ParticleContainer Properties
Problem: Changing scale when dynamicProperties.scale = false has no effect.
Solution:
const container = new ParticleContainer({
dynamicProperties: {
position: true,
scale: true, // Enable if you need to update
rotation: true,
color: true
}
});
// If properties are static but you change them, call update:
container.update();
Pitfall 3: Excessive Filter Usage
Problem: Filters are expensive; too many cause performance issues.
Solution:
// Limit filter usage
sprite.filters = [blurFilter]; // 1-2 filters max
// Use filterArea to constrain processing
sprite.filterArea = new Rectangle(0, 0, sprite.width, sprite.height);
// Bake filters into textures when possible
const filteredTexture = renderer.filters.generateFilteredTexture({
texture,
filters: [blurFilter]
});
Pitfall 4: Frequent Text Updates
Problem: Updating Text re-generates texture every time.
Solution:
// Use BitmapText for frequently changing text
const bitmapText = new BitmapText({ text: 'Score: 0' });
// Reduce resolution for less memory
text.resolution = 1; // Lower than device pixel ratio
Pitfall 5: Graphics Clear() Without Redraw
Problem: Calling clear() removes all geometry but doesn’t automatically redraw.
Solution:
graphics.clear(); // Remove all shapes
// Redraw new shapes
graphics.rect(0, 0, 100, 100).fill('blue');
Pitfall 6: Not Using Asset Loading
Problem: Creating sprites from URLs causes async issues.
Solution:
// DON'T:
const sprite = Sprite.from('image.png'); // May load asynchronously
// DO:
const texture = await Assets.load('image.png');
const sprite = new Sprite(texture);
Resources
- Official Site: https://pixijs.com
- API Documentation: https://pixijs.download/release/docs/
- Examples: https://pixijs.io/examples/
- GitHub: https://github.com/pixijs/pixijs
- Filters Library: @pixi/filter-* packages
- Community: https://github.com/pixijs/pixijs/discussions
Related Skills
- threejs-webgl: For 3D graphics; PixiJS can provide 2D UI overlays
- gsap-scrolltrigger: For animating PixiJS properties with scroll
- motion-framer: For React component animations alongside PixiJS canvas
- react-three-fiber: Similar React integration patterns
Summary
PixiJS excels at high-performance 2D rendering with WebGL acceleration. Key strengths:
- Performance: Render 100,000+ sprites at 60 FPS
- ParticleContainer: 10x faster for static properties
- Filters: WebGL-powered visual effects
- Graphics API: Intuitive vector drawing
- Asset Management: Robust texture and sprite sheet handling
Use for particle systems, 2D games, data visualizations, and interactive canvas applications where performance is critical.