.factory/skills/pixijs-2d/SKILL.md
Fast, lightweight 2D rendering engine for creating interactive graphics, particle effects, and canvas-based applications using WebGL/WebGPU. Use this skill when building 2D games, particle systems, interactive canvases, sprite animations, or UI overlays on 3D scenes. Triggers on tasks involving PixiJS, 2D rendering, sprite sheets, particle effects, filters, or high-performance canvas graphics. Alternative to Canvas2D with WebGL acceleration for rendering thousands of sprites at 60 FPS.
npx skillsauth add freshtechbro/claudedesignskills pixijs-2dInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Fast, lightweight 2D rendering engine for creating interactive graphics, particle effects, and canvas-based applications using WebGL/WebGPU.
Trigger this skill when you encounter:
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.
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 dimensionsCore 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');
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>');
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.
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-aliasingDisplay 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).
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;
});
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');
});
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;
}
});
});
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;
});
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;
});
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!');
};
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);
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} />;
}
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();
// 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 }));
}
// 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
});
// Destroy textures when done
texture.destroy();
// Batch destruction with delays to prevent frame drops
textures.forEach((tex, i) => {
setTimeout(() => tex.destroy(), Math.random() * 100);
});
sprite.cullable = true; // Skip rendering if outside viewport
// Use CullerPlugin
import { CullerPlugin } from 'pixi.js';
// Convert complex graphics to texture for faster rendering
const complexShape = new Graphics();
// ... draw many shapes
complexShape.cacheAsBitmap = true; // Renders to texture once
const app = new Application();
await app.init({
antialias: false, // Disable on mobile for performance
resolution: 1, // Lower resolution on low-end devices
autoDensity: true
});
// 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}`;
});
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();
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();
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]
});
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
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');
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);
PixiJS excels at high-performance 2D rendering with WebGL acceleration. Key strengths:
Use for particle systems, 2D games, data visualizations, and interactive canvas applications where performance is critical.
development
Meta-skill for combining Three.js, GSAP ScrollTrigger, React Three Fiber, Motion, and React Spring for complex 3D web experiences. Use when building applications that integrate multiple 3D and animation libraries, requiring architecture patterns, state management, and performance optimization across the stack. Triggers on tasks involving library integration, multi-library architectures, scroll-driven 3D experiences, physics-based 3D animations, or complex interactive 3D applications.
development
Comprehensive skill for Three.js 3D web development. Use this skill when building interactive 3D scenes, WebGL/WebGPU applications, product configurators, 3D visualizations, or immersive web experiences. Triggers on tasks involving Three.js, 3D rendering, scenes, cameras, meshes, materials, lights, animations, textures, or WebGL/WebGPU rendering.
tools
Comprehensive skill for Adobe Substance 3D Painter texturing and material creation workflow. Use this skill when creating PBR materials, exporting textures for web/game engines, optimizing 3D assets for real-time rendering, or automating texture workflows. Triggers on tasks involving Substance 3D Painter, PBR texturing, material creation, texture export for Three.js, Babylon.js, Unity, Unreal, glTF optimization, or Python API automation. Creates optimized textures for threejs-webgl, react-three-fiber, and babylonjs-engine materials.
tools
Browser-based 3D design tool with visual editor, animation, and web export. Use this skill when creating 3D scenes without code, designing interactive web experiences, prototyping 3D UI, exporting to React/web, or building designer-friendly 3D content. Triggers on tasks involving Spline, no-code 3D, visual 3D editor, 3D animation, state-based interactions, React Spline integration, or scene export. Alternative to Three.js for designers who prefer visual tools over code.