
Pixijs 2d
- 1.5k installs
- 629 repo stars
- Updated November 20, 2025
- freshtechbro/claudedesignskills
pixijs-2d is an agent skill for build high-performance 2d webgl games and interactive graphics with pixijs.
About
The pixijs-2d skill is designed for build high-performance 2D WebGL games and interactive graphics with PixiJS. PixiJS 2D Rendering Skill Fast, lightweight 2D rendering engine for creating interactive graphics, particle effects, and canvas-based applications using WebGL/WebGPU. Don't use for: 3D graphics (use Three.js/R3F), simple animations (use Motion/GSAP), basic DOM manipulation. Invoke when the user builds PixiJS 2D graphics, sprites, or WebGL interactive scenes.
- "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".
Pixijs 2d by the numbers
- 1,546 all-time installs (skills.sh)
- +94 installs in the week ending Jul 29, 2026 (Skillselion tracking)
- Ranked #287 of 2,244 Frontend Development skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
pixijs-2d capabilities & compatibility
- Capabilities
- "create 2d particle effects" or "animated partic · "2d sprite animation" or "sprite sheet handling" · "interactive canvas graphics" or "2d game" · "ui overlays on 3d scenes" or "hud layer"
- Use cases
- frontend
What pixijs-2d says it does
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,
Fast, lightweight 2D rendering engine for creating interactive graphics, particle effects, and canvas-based applications using WebGL/WebGPU. Use this skill when
npx skills add https://github.com/freshtechbro/claudedesignskills --skill pixijs-2dAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.5k |
|---|---|
| repo stars | ★ 629 |
| Security audit | 3 / 3 scanners passed |
| Last updated | November 20, 2025 |
| Repository | freshtechbro/claudedesignskills ↗ |
How do I build high-performance 2d webgl games and interactive graphics with pixijs?
Build high-performance 2D WebGL games and interactive graphics with PixiJS.
Who is it for?
Frontend developers creating PixiJS sprites, scenes, and WebGL interactions.
Skip if: Skip for Three.js 3D scenes without PixiJS 2D rendering needs.
When should I use this skill?
User builds PixiJS 2D graphics, sprites, or WebGL interactive scenes.
What you get
Completed pixijs-2d workflow with documented commands, files, and expected deliverables.
- PixiJS application patterns
- Shader and particle examples
- Performance-tuned rendering code
By the numbers
- Documents 12 PixiJS topic areas from basics through advanced techniques
Files
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:
1. Performance: Render 100,000+ sprites at 60 FPS 2. ParticleContainer: 10x faster for static properties 3. Filters: WebGL-powered visual effects 4. Graphics API: Intuitive vector drawing 5. Asset Management: Robust texture and sprite sheet handling
Use for particle systems, 2D games, data visualizations, and interactive canvas applications where performance is critical.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PixiJS Starter</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<!-- UI Overlay -->
<div id="ui-overlay">
<div id="info-panel">
<h1>PixiJS Starter</h1>
<p>High-performance 2D rendering</p>
</div>
<div id="stats-panel">
<div class="stat">
<span class="label">FPS:</span>
<span id="fps">--</span>
</div>
<div class="stat">
<span class="label">Sprites:</span>
<span id="sprite-count">--</span>
</div>
<div class="stat">
<span class="label">Draw Calls:</span>
<span id="draw-calls">--</span>
</div>
</div>
<div id="controls-panel">
<button id="toggle-stats">Toggle Stats</button>
<button id="add-sprites">Add Sprites</button>
<button id="clear-sprites">Clear</button>
</div>
</div>
<!-- PixiJS CDN -->
<script src="https://pixijs.download/release/pixi.js"></script>
<script src="main.js"></script>
</body>
</html>
/**
* PixiJS Starter Project
* Main application initialization
*/
(async () => {
// Create PixiJS application
const app = new PIXI.Application();
await app.init({
width: window.innerWidth,
height: window.innerHeight,
backgroundColor: 0x1a1a2e,
antialias: true,
resolution: window.devicePixelRatio || 1,
autoDensity: true
});
document.body.appendChild(app.canvas);
// Create sprite container
const spriteContainer = new PIXI.Container();
app.stage.addChild(spriteContainer);
// Sprite collection
const sprites = [];
// Create sprite texture
function createSpriteTexture(color) {
const graphics = new PIXI.Graphics();
graphics.circle(25, 25, 25).fill(color);
return app.renderer.generateTexture(graphics);
}
const textures = [
createSpriteTexture(0xe74c3c),
createSpriteTexture(0x3498db),
createSpriteTexture(0x2ecc71),
createSpriteTexture(0xf39c12),
createSpriteTexture(0x9b59b6)
];
// Add sprites function
function addSprites(count = 10) {
for (let i = 0; i < count; i++) {
const texture = textures[Math.floor(Math.random() * textures.length)];
const sprite = new PIXI.Sprite(texture);
sprite.anchor.set(0.5);
sprite.position.set(
Math.random() * app.screen.width,
Math.random() * app.screen.height
);
sprite.scale.set(Math.random() * 0.5 + 0.5);
// Velocity
sprite.vx = (Math.random() - 0.5) * 2;
sprite.vy = (Math.random() - 0.5) * 2;
// Interactive
sprite.eventMode = 'static';
sprite.cursor = 'pointer';
sprite.on('pointerdown', () => {
sprite.tint = Math.random() * 0xffffff;
});
spriteContainer.addChild(sprite);
sprites.push(sprite);
}
updateSpriteCount();
}
// Clear sprites function
function clearSprites() {
sprites.forEach(sprite => sprite.destroy());
sprites.length = 0;
spriteContainer.removeChildren();
updateSpriteCount();
}
// Update sprite count display
function updateSpriteCount() {
document.getElementById('sprite-count').textContent = sprites.length;
}
// Setup UI controls
let statsVisible = true;
document.getElementById('toggle-stats').addEventListener('click', () => {
statsVisible = !statsVisible;
document.getElementById('stats-panel').classList.toggle('hidden');
});
document.getElementById('add-sprites').addEventListener('click', () => {
addSprites(20);
});
document.getElementById('clear-sprites').addEventListener('click', () => {
clearSprites();
});
// Update loop
app.ticker.add((ticker) => {
// Move sprites
sprites.forEach(sprite => {
sprite.x += sprite.vx * ticker.deltaTime;
sprite.y += sprite.vy * ticker.deltaTime;
// Bounce off edges
if (sprite.x < 0 || sprite.x > app.screen.width) {
sprite.vx *= -1;
}
if (sprite.y < 0 || sprite.y > app.screen.height) {
sprite.vy *= -1;
}
// Keep within bounds
sprite.x = Math.max(0, Math.min(app.screen.width, sprite.x));
sprite.y = Math.max(0, Math.min(app.screen.height, sprite.y));
// Rotate
sprite.rotation += 0.01 * ticker.deltaTime;
});
// Update stats
if (statsVisible) {
document.getElementById('fps').textContent = Math.round(app.ticker.FPS);
document.getElementById('draw-calls').textContent = app.renderer.stats.drawCalls.total || 0;
}
});
// Handle window resize
window.addEventListener('resize', () => {
app.renderer.resize(window.innerWidth, window.innerHeight);
});
// Initial sprites
addSprites(50);
console.log('PixiJS application initialized');
console.log('Canvas size:', app.screen.width, 'x', app.screen.height);
console.log('Resolution:', app.renderer.resolution);
})();
PixiJS Starter Template
A modern, production-ready PixiJS starter template with interactive sprites, real-time performance monitoring, and responsive UI controls.
Features
- High-Performance Rendering: Uses PixiJS v8+ with WebGL/WebGPU
- Interactive Sprites: Click to change colors, drag and interact
- Physics Simulation: Bouncing sprites with velocity and collision detection
- Performance Monitoring: Real-time FPS, sprite count, and draw call tracking
- Responsive Design: Mobile-friendly UI with glassmorphism effects
- Modern UI: Clean, professional interface with gradient accents
- Easy Customization: Well-structured code for quick modifications
Quick Start
1. Local Development
Simply open index.html in a modern web browser:
# Using Python's built-in server (recommended)
python3 -m http.server 8000
# Or using Node.js http-server
npx http-server -p 8000
# Then open http://localhost:80002. Live Server (VS Code)
If using VS Code with the Live Server extension:
1. Right-click on index.html 2. Select "Open with Live Server"
3. Production Deployment
For production, serve the files through any static hosting:
- Vercel:
vercel --prod - Netlify: Drag and drop the folder
- GitHub Pages: Push to repository and enable Pages
- AWS S3: Upload as static website
Project Structure
starter_pixijs/
├── index.html # Main HTML structure
├── styles.css # Responsive styling with glassmorphism
├── main.js # PixiJS application logic
└── README.md # This fileUsage
Controls
- Toggle Stats: Show/hide performance statistics panel
- Add Sprites: Add 20 random sprites to the canvas
- Clear: Remove all sprites from the canvas
Interactions
- Click Sprites: Change sprite color randomly
- Watch Physics: Sprites bounce off edges automatically
Customization
Change Background Color
In main.js line 10:
await app.init({
backgroundColor: 0x1a1a2e, // Change this hex color
// ...
});Modify Sprite Colors
In main.js lines 35-40, edit the color palette:
const textures = [
createSpriteTexture(0xe74c3c), // Red
createSpriteTexture(0x3498db), // Blue
createSpriteTexture(0x2ecc71), // Green
createSpriteTexture(0xf39c12), // Orange
createSpriteTexture(0x9b59b6) // Purple
];Adjust Sprite Size
In main.js line 31, change the circle radius:
graphics.circle(25, 25, 25).fill(color); // Last parameter is radiusChange Initial Sprite Count
In main.js line 140:
addSprites(50); // Change from 50 to your desired countModify Physics Behavior
In main.js lines 57-58, adjust velocity ranges:
sprite.vx = (Math.random() - 0.5) * 2; // Horizontal speed
sprite.vy = (Math.random() - 0.5) * 2; // Vertical speedDisable Rotation
In main.js, comment out or remove line 124:
// sprite.rotation += 0.01 * ticker.deltaTime;Advanced Customization
Add Sprite Textures from Images
Replace the procedural graphics with image textures:
// Load texture from image
const texture = await PIXI.Assets.load('path/to/sprite.png');
// Create sprite
const sprite = new PIXI.Sprite(texture);Add Filters and Effects
Apply blur, glow, or other effects:
import { BlurFilter } from 'pixi.js';
const blurFilter = new BlurFilter();
blurFilter.strength = 8;
sprite.filters = [blurFilter];Implement Sprite Pooling
For better performance with many sprites:
class SpritePool {
constructor(texture, size = 100) {
this.available = [];
this.active = [];
for (let i = 0; i < size; i++) {
const sprite = new PIXI.Sprite(texture);
sprite.visible = false;
this.available.push(sprite);
}
}
spawn(x, y) {
let sprite = this.available.pop();
if (!sprite) {
sprite = new PIXI.Sprite(this.texture);
}
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);
}
}
}
const pool = new SpritePool(texture);
const sprite = pool.spawn(100, 100);
// Later: pool.despawn(sprite);Add Particle Effects
Use ParticleContainer for thousands of sprites:
const particles = new PIXI.ParticleContainer(10000, {
position: true,
rotation: true,
scale: true,
tint: true
});
for (let i = 0; i < 10000; i++) {
const particle = new PIXI.Sprite(texture);
particle.x = Math.random() * app.screen.width;
particle.y = Math.random() * app.screen.height;
particles.addChild(particle);
}
app.stage.addChild(particles);Add Custom Shaders
Create custom visual effects with GLSL:
import { Filter, GlProgram } from 'pixi.js';
const fragment = `
in vec2 vTextureCoord;
uniform sampler2D uTexture;
uniform float uTime;
void main() {
vec2 coord = vTextureCoord;
coord.x += sin(coord.y * 10.0 + uTime) * 0.01;
gl_FragColor = texture(uTexture, coord);
}
`;
const waveFilter = new Filter({
glProgram: new GlProgram({ fragment }),
resources: {
waveUniforms: {
uTime: { value: 0, type: 'f32' }
}
}
});
// Update in ticker
app.ticker.add(() => {
waveFilter.resources.waveUniforms.uniforms.uTime += 0.1;
});
sprite.filters = [waveFilter];Performance Tips
For Desktop (High-End)
- Increase sprite count for stress testing
- Enable antialiasing for smoother edges
- Use higher resolution textures
- Add complex filters and effects
await app.init({
antialias: true,
resolution: 2, // Higher resolution
// ...
});
addSprites(500); // More spritesFor Mobile (Low-End)
- Reduce sprite count
- Disable antialiasing
- Use lower resolution
- Avoid heavy filters
await app.init({
antialias: false,
resolution: 1,
// ...
});
addSprites(50); // Fewer spritesGeneral Optimization
1. Use ParticleContainer for static sprites (10x faster) 2. Enable cacheAsBitmap for complex static graphics 3. Minimize draw calls with texture atlases 4. Cull off-screen objects for large scenes 5. Pool objects to avoid garbage collection 6. Limit filters to specific areas with filterArea
Troubleshooting
Issue: Black screen or no rendering
Solution: Check browser console for errors. Ensure:
- PixiJS CDN is loading correctly
- No JavaScript errors in console
- Browser supports WebGL (check
https://get.webgl.org/)
Issue: Low FPS on mobile
Solution: Reduce sprite count and disable antialiasing:
await app.init({
antialias: false,
resolution: 1
});
addSprites(25); // Fewer spritesIssue: Sprites disappearing at edges
Solution: Ensure sprites are kept within bounds (lines 120-121 in main.js handle this)
Issue: Memory leaks over time
Solution: Properly destroy sprites when clearing:
function clearSprites() {
sprites.forEach(sprite => {
sprite.destroy({ texture: false }); // Keep texture
});
sprites.length = 0;
spriteContainer.removeChildren();
}Browser Support
- Chrome/Edge: Full support (recommended)
- Firefox: Full support
- Safari: Full support (iOS 15+)
- Mobile browsers: Supported with reduced features
Requires WebGL support. Check compatibility at caniuse.com/webgl.
Next Steps
Learning Resources
Extend the Template
1. Add Sprite Sheet Animations: Use AnimatedSprite for frame-based animation 2. Implement Collision Detection: Check sprite overlaps and interactions 3. Add Sound Effects: Integrate Howler.js or Web Audio API 4. Create Game Logic: Add scoring, levels, or gameplay mechanics 5. Integrate with React: Use @pixi/react for component-based approach
Example Extensions
Collision Detection:
function checkCollision(sprite1, sprite2) {
const bounds1 = sprite1.getBounds();
const bounds2 = sprite2.getBounds();
return bounds1.x < bounds2.x + bounds2.width &&
bounds1.x + bounds1.width > bounds2.x &&
bounds1.y < bounds2.y + bounds2.height &&
bounds1.y + bounds1.height > bounds2.y;
}
app.ticker.add(() => {
for (let i = 0; i < sprites.length; i++) {
for (let j = i + 1; j < sprites.length; j++) {
if (checkCollision(sprites[i], sprites[j])) {
// Handle collision
}
}
}
});Sprite Sheet Animation:
// Load sprite sheet
const sheet = await PIXI.Assets.load('spritesheet.json');
// Create animated sprite
const animatedSprite = new PIXI.AnimatedSprite(sheet.animations['run']);
animatedSprite.animationSpeed = 0.1;
animatedSprite.play();
app.stage.addChild(animatedSprite);React Integration:
import { Stage, Container, Sprite } from '@pixi/react';
function App() {
return (
<Stage width={800} height={600}>
<Container>
<Sprite texture={texture} x={100} y={100} />
</Container>
</Stage>
);
}Deployment
Static Hosting
Vercel:
npm install -g vercel
vercel --prodNetlify:
npm install -g netlify-cli
netlify deploy --prod --dir .GitHub Pages:
git init
git add .
git commit -m "Initial commit"
git branch -M main
git remote add origin https://github.com/username/repo.git
git push -u origin main
# Enable Pages in repository settingsCDN Considerations
The template uses PixiJS from CDN:
<script src="https://pixijs.download/release/pixi.js"></script>For production, consider: 1. Self-hosting for better caching and control 2. npm installation for bundled builds 3. Specific version to avoid breaking changes
npm approach:
npm install pixi.js// main.js
import * as PIXI from 'pixi.js';
// Use bundler like Vite or WebpackLicense
This starter template is provided as-is for learning and development purposes.
PixiJS is MIT licensed. See PixiJS GitHub for details.
Support
For PixiJS questions:
---
Happy Coding! 🎨✨
/* Reset and Base Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: #1a1a2e;
color: #fff;
}
/* Canvas Styles */
canvas {
display: block;
width: 100%;
height: 100%;
}
/* UI Overlay */
#ui-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 10;
}
#ui-overlay > * {
pointer-events: auto;
}
/* Info Panel */
#info-panel {
position: absolute;
top: 20px;
left: 20px;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(10px);
padding: 20px;
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
#info-panel h1 {
font-size: 24px;
font-weight: 600;
margin-bottom: 8px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
#info-panel p {
font-size: 14px;
opacity: 0.8;
}
/* Stats Panel */
#stats-panel {
position: absolute;
top: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(10px);
padding: 15px;
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.1);
min-width: 180px;
}
#stats-panel.hidden {
display: none;
}
.stat {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
font-size: 14px;
}
.stat:last-child {
margin-bottom: 0;
}
.stat .label {
opacity: 0.7;
margin-right: 15px;
}
.stat span:last-child {
font-weight: 600;
font-variant-numeric: tabular-nums;
}
/* Controls Panel */
#controls-panel {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 10px;
}
button {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
color: #fff;
padding: 12px 24px;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
button:hover {
background: rgba(255, 255, 255, 0.2);
border-color: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
}
button:active {
transform: translateY(0);
}
/* Mobile Responsive */
@media (max-width: 768px) {
#info-panel {
top: 10px;
left: 10px;
padding: 15px;
}
#info-panel h1 {
font-size: 20px;
}
#info-panel p {
font-size: 12px;
}
#stats-panel {
top: 10px;
right: 10px;
padding: 10px;
min-width: 150px;
}
.stat {
font-size: 12px;
}
#controls-panel {
bottom: 10px;
flex-direction: column;
gap: 8px;
}
button {
padding: 10px 20px;
font-size: 13px;
}
}
PixiJS API Reference
Complete API reference for PixiJS v8+ core classes and methods.
---
Table of Contents
1. Application 2. Sprite 3. Texture 4. Graphics 5. Container 6. ParticleContainer 7. Filters 8. Text 9. Assets 10. Renderer 11. DisplayObject 12. Events
---
Application
Core application class that manages the renderer, stage, and update loop.
Constructor
new Application()Methods
init(options)
Initialize the application with configuration options.
await app.init({
width: 800,
height: 600,
backgroundColor: 0x1099bb,
backgroundAlpha: 1,
antialias: true,
resolution: window.devicePixelRatio || 1,
autoDensity: true,
powerPreference: 'high-performance',
hello: true // Show PixiJS banner in console
});Options:
width: number- Canvas width (default: 800)height: number- Canvas height (default: 600)backgroundColor: number- Background color (default: 0x000000)backgroundAlpha: number- Background alpha 0-1 (default: 1)antialias: boolean- Enable antialiasing (default: false)resolution: number- Device pixel ratio (default: 1)autoDensity: boolean- Adjust CSS pixel size automaticallypowerPreference: string- 'high-performance' | 'low-power' | 'default'hello: boolean- Show PixiJS banner (default: false)
destroy(removeView, stageOptions)
Destroy the application and release resources.
app.destroy(true, { children: true, texture: true });Parameters:
removeView: boolean- Remove canvas from DOM (default: false)stageOptions: object- Options for stage destructionchildren: boolean- Destroy all childrentexture: boolean- Destroy texturesbaseTexture: boolean- Destroy base textures
resizeCanvas()
Resize canvas to fill window.
window.addEventListener('resize', () => {
app.resizeCanvas();
});Properties
app.stage: Container // Root display object container
app.renderer: Renderer // WebGL/WebGPU renderer instance
app.ticker: Ticker // Update loop manager
app.canvas: HTMLCanvasElement // Canvas element
app.screen: Rectangle // Screen dimensions
app.view: HTMLCanvasElement // Alias for canvas (deprecated)Plugins
// Ticker Plugin - manages update loop
app.ticker.add((ticker) => {
// Update logic
sprite.rotation += 0.01 * ticker.deltaTime;
});
app.ticker.stop();
app.ticker.start();
app.ticker.speed = 0.5; // Half speed
// Resize Plugin
app.resizeTo = window; // Auto-resize to window
// Culler Plugin - automatic viewport culling
app.cullable = true;API References:
- TickerPlugin: https://pixijs.download/release/docs/app.TickerPlugin.html
- ResizePlugin: https://pixijs.download/release/docs/app.ResizePlugin.html
- CullerPlugin: https://pixijs.download/release/docs/app.CullerPlugin.html
---
Sprite
Visual element that displays a texture.
Constructor
new Sprite(texture: Texture)
Sprite.from(source: string | Texture) // Convenience methodProperties
sprite.texture: Texture // The texture to display
sprite.anchor: ObservablePoint // Pivot point (0-1, default: 0,0)
sprite.tint: number // Color tint (0xRRGGBB)
sprite.blendMode: BLEND_MODES // How sprite blends with background
// Transform properties (inherited from DisplayObject)
sprite.position: ObservablePoint // x, y position
sprite.scale: ObservablePoint // x, y scale
sprite.rotation: number // Rotation in radians
sprite.pivot: ObservablePoint // Rotation pivot point
sprite.skew: ObservablePoint // x, y skew
// Visibility
sprite.alpha: number // Opacity (0-1)
sprite.visible: boolean // Show/hide
sprite.renderable: boolean // Should render
// Interaction
sprite.eventMode: string // 'none' | 'passive' | 'static' | 'dynamic'
sprite.cursor: string // CSS cursor
sprite.hitArea: Rectangle | Circle | Polygon // Custom hit area
// Performance
sprite.cullable: boolean // Enable viewport culling
sprite.cacheAsBitmap: boolean // Convert to texture for performanceMethods
// Anchor
sprite.anchor.set(x, y)
sprite.anchor.set(0.5) // Center (shorthand)
// Position
sprite.position.set(x, y)
sprite.setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY)
// Bounds
sprite.getBounds()
sprite.getLocalBounds()
// Destroy
sprite.destroy({ children: true, texture: false, baseTexture: false })Example
import { Sprite, Texture } from 'pixi.js';
const texture = Texture.from('bunny.png');
const sprite = new Sprite(texture);
sprite.anchor.set(0.5);
sprite.position.set(400, 300);
sprite.scale.set(2);
sprite.rotation = Math.PI / 4;
sprite.tint = 0xff0000;
sprite.alpha = 0.8;
app.stage.addChild(sprite);---
Texture
Image data that can be rendered by Sprites and Graphics.
Static Methods
Texture.from(source: string | HTMLImageElement | HTMLCanvasElement)
Texture.fromURL(url: string, options?: object)
Texture.fromBuffer(buffer: Uint8Array, width: number, height: number)Properties
texture.width: number // Texture width
texture.height: number // Texture height
texture.baseTexture: BaseTexture // Underlying GPU texture
texture.frame: Rectangle // Region of baseTexture to use
texture.source: TextureSource // Source dataMethods
texture.destroy(destroyBase?: boolean)
texture.update() // Update from source
texture.clone() // Create copyExample
import { Texture, Assets } from 'pixi.js';
// Load texture
const texture = await Assets.load('image.png');
// From URL
const tex = Texture.from('https://example.com/image.png');
// From canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// ... draw on canvas
const canvasTex = Texture.from(canvas);
// Destroy
texture.destroy(true); // Also destroy baseTextureAPI Reference: https://pixijs.download/release/docs/rendering.Texture.html
---
Graphics
API for drawing vector shapes programmatically.
Constructor
new Graphics()
new Graphics(context: GraphicsContext) // Share geometryShape Methods
All shape methods return this for chaining.
graphics.rect(x, y, width, height)
graphics.circle(x, y, radius)
graphics.ellipse(x, y, radiusX, radiusY)
graphics.roundRect(x, y, width, height, radius)
graphics.poly(points: number[] | Point[])
graphics.star(x, y, points, radius, innerRadius?, rotation?)Path Methods
graphics.moveTo(x, y)
graphics.lineTo(x, y)
graphics.bezierCurveTo(cpX, cpY, cpX2, cpY2, toX, toY)
graphics.quadraticCurveTo(cpX, cpY, toX, toY)
graphics.arcTo(x1, y1, x2, y2, radius)
graphics.arc(x, y, radius, startAngle, endAngle, anticlockwise?)
graphics.closePath()Fill & Stroke
// Fill
graphics.fill(color: number | string)
graphics.fill({ color, alpha })
graphics.fill(texture: Texture)
// Stroke
graphics.stroke({ width, color, alpha, alignment, cap, join })
// Options
{
width: number, // Line width
color: number | string, // Line color
alpha: number, // Line opacity (0-1)
alignment: number, // 0=inner, 0.5=middle, 1=outer
cap: string, // 'butt' | 'round' | 'square'
join: string // 'miter' | 'round' | 'bevel'
}Holes
graphics.rect(0, 0, 100, 100).fill('red')
.beginHole()
.circle(50, 50, 20)
.endHole();SVG Support
graphics.svg('<svg><path d="M 100 350 q 150 -300 300 0" /></svg>');Other Methods
graphics.clear() // Remove all shapes
graphics.clone() // Create copy
graphics.destroy(options) // Destroy and release memory
// Context sharing
const context = new GraphicsContext().circle(50, 50, 30).fill('red');
const g1 = new Graphics(context);
const g2 = new Graphics(context); // Shares same geometryProperties
graphics.context: GraphicsContext // Drawing instructions
graphics.pixelLine: boolean // Force 1px line width regardless of scale
graphics.fillStyle: FillStyle // Current fill style
graphics.lineStyle: StrokeStyle // Current line styleExample
import { Graphics } from 'pixi.js';
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
graphics.star(600, 300, 5, 50).fill(0xffdf00);
// Custom path
graphics
.moveTo(100, 400)
.bezierCurveTo(150, 300, 250, 300, 300, 400)
.stroke({ width: 3, color: 'white' });
// Hole
graphics.rect(450, 400, 150, 100).fill('red')
.beginHole()
.circle(525, 450, 30)
.endHole();
app.stage.addChild(graphics);API References:
- Graphics: https://pixijs.download/release/docs/scene.Graphics.html
- GraphicsContext: https://pixijs.download/release/docs/scene.GraphicsContext.html
- FillStyle: https://pixijs.download/release/docs/scene.FillStyle.html
- StrokeStyle: https://pixijs.download/release/docs/scene.StrokeStyle.html
---
Container
Display object that can contain children (like a group).
Constructor
new Container()Children Management
container.addChild(child: DisplayObject)
container.addChildAt(child: DisplayObject, index: number)
container.removeChild(child: DisplayObject)
container.removeChildAt(index: number)
container.removeChildren(beginIndex?, endIndex?)
container.getChildAt(index: number)
container.getChildIndex(child: DisplayObject)
container.setChildIndex(child: DisplayObject, index: number)
container.swapChildren(child1: DisplayObject, child2: DisplayObject)Properties
container.children: DisplayObject[] // Array of children
container.width: number // Combined width of children
container.height: number // Combined height of children
container.sortableChildren: boolean // Enable z-index sorting
container.interactiveChildren: boolean // Enable child interactionFilters
container.filters: Filter[] // Array of filters
container.filterArea: Rectangle // Filter bounding boxIteration
for (const child of container.children) {
// Process child
}
container.children.forEach(child => {
// Process child
});Example
import { Container, Sprite } from 'pixi.js';
const container = new Container();
container.position.set(100, 100);
// Add children
const sprite1 = Sprite.from('image1.png');
const sprite2 = Sprite.from('image2.png');
sprite2.x = 50;
container.addChild(sprite1, sprite2);
// Z-index sorting
container.sortableChildren = true;
sprite1.zIndex = 2;
sprite2.zIndex = 1; // Renders behind sprite1
app.stage.addChild(container);---
ParticleContainer
Optimized container for rendering thousands of sprites with limited transform capabilities.
Constructor
new ParticleContainer(options?: ParticleContainerOptions)Options:
{
maxSize: number, // Max particles (default: 1500)
dynamicProperties: {
position: boolean, // Allow position updates (default: true)
scale: boolean, // Allow scale updates (default: false)
rotation: boolean, // Allow rotation updates (default: false)
color: boolean // Allow tint/alpha updates (default: false)
}
}Methods
container.addParticle(particle: Particle)
container.removeParticle(particle: Particle)
container.update() // Call if changing static properties
container.destroy()Properties
container.maxSize: number // Maximum particle count
container.dynamicProperties: object // Which properties can change
container.particleChildren: Particle[] // Array of particlesParticle Interface
interface IParticle {
x: number;
y: number;
scaleX: number;
scaleY: number;
anchorX: number;
anchorY: number;
rotation: number;
color: number; // Tint
texture: Texture;
}
// Create particle
const particle = new Particle({
texture: Texture.from('spark.png'),
x: 100,
y: 200,
scaleX: 0.5,
scaleY: 0.5,
rotation: 0,
tint: 0xffffff,
alpha: 1.0
});Example
import { ParticleContainer, Particle, Texture } from 'pixi.js';
const texture = Texture.from('particle.png');
const particles = new ParticleContainer({
maxSize: 10000,
dynamicProperties: {
position: true, // Update positions
scale: true, // Update scale
rotation: false, // Static rotation
color: false // Static color
}
});
for (let i = 0; i < 10000; i++) {
const particle = new Particle({
texture,
x: Math.random() * 800,
y: Math.random() * 600
});
particles.addParticle(particle);
}
app.stage.addChild(particles);
// Update loop
app.ticker.add(() => {
particles.particleChildren.forEach(p => {
p.y += 1; // Move down
if (p.y > 600) p.y = 0;
});
});---
Filters
WebGL shader-based effects applied to display objects.
Built-in Filters
BlurFilter
import { BlurFilter } from 'pixi.js';
const blur = new BlurFilter({
strength: 8, // Blur amount (default: 8)
quality: 4, // Iterations (default: 4)
kernelSize: 5 // Sample size: 5, 7, 9, 11, 13, 15
});
sprite.filters = [blur];ColorMatrixFilter
import { ColorMatrixFilter } from 'pixi.js';
const colorMatrix = new ColorMatrixFilter();
// Presets
colorMatrix.greyscale(0.5); // 0-1
colorMatrix.sepia();
colorMatrix.blackAndWhite();
colorMatrix.contrast(1.5); // >1 increases
colorMatrix.saturate(2); // -1 to 1
colorMatrix.brightness(1.2); // >1 brightens
colorMatrix.hue(45); // Rotate hue (degrees)
colorMatrix.negative();
colorMatrix.kodachrome();
colorMatrix.technicolor();
colorMatrix.polaroid();
colorMatrix.vintage();
sprite.filters = [colorMatrix];DisplacementFilter
import { DisplacementFilter, Sprite } from 'pixi.js';
const displacementSprite = Sprite.from('displacement.jpg');
const displacementFilter = new DisplacementFilter({
sprite: displacementSprite,
scale: 50 // Displacement amount
});
sprite.filters = [displacementFilter];
// Animate displacement
app.ticker.add(() => {
displacementSprite.x += 1;
});AlphaFilter
import { AlphaFilter } from 'pixi.js';
const alphaFilter = new AlphaFilter(0.5); // 0-1
container.filters = [alphaFilter]; // Flattens alpha across childrenNoiseFilter
import { NoiseFilter } from 'pixi.js';
const noise = new NoiseFilter({
noise: 0.5, // Amount (0-1)
seed: Math.random()
});
sprite.filters = [noise];FXAAFilter
import { FXAAFilter } from 'pixi.js';
const fxaa = new FXAAFilter();
sprite.filters = [fxaa]; // Anti-aliasingCustom Filters
import { Filter, GlProgram } from 'pixi.js';
const vertex = `...`; // Vertex shader
const fragment = `...`; // Fragment shader
const customFilter = new Filter({
glProgram: new GlProgram({ vertex, fragment }),
resources: {
customUniforms: {
uTime: { value: 0.0, type: 'f32' },
uColor: { value: [1.0, 0.0, 0.0], type: 'vec3<f32>' }
}
}
});
sprite.filters = [customFilter];
// Update uniforms
app.ticker.add((ticker) => {
customFilter.resources.customUniforms.uniforms.uTime += 0.01 * ticker.deltaTime;
});Filter Optimization
// Specify filterArea to avoid runtime measurement
sprite.filterArea = new Rectangle(0, 0, sprite.width, sprite.height);
// Release filters
sprite.filters = null;
// Generate filtered texture (apply once)
const filteredTexture = renderer.filters.generateFilteredTexture({
texture: originalTexture,
filters: [blurFilter]
});API References:
- BlurFilter: @pixi/filter-blur
- ColorMatrixFilter: @pixi/filter-color-matrix
- DisplacementFilter: @pixi/filter-displacement
- AlphaFilter: @pixi/filter-alpha
- NoiseFilter: @pixi/filter-noise
- FXAAFilter: @pixi/filter-fxaa
---
Text
Render styled text as a texture.
Text
Standard text rendering:
import { Text, TextStyle } from 'pixi.js';
const style = new TextStyle({
fontFamily: 'Arial',
fontSize: 36,
fontStyle: 'italic',
fontWeight: 'bold',
fill: '#ffffff',
stroke: { color: '#000000', width: 4 },
dropShadow: {
alpha: 0.5,
angle: Math.PI / 6,
blur: 4,
color: '#000000',
distance: 6
},
wordWrap: true,
wordWrapWidth: 400,
align: 'center',
filters: [new BlurFilter()] // Bake filter into texture
});
const text = new Text({
text: 'Hello PixiJS!',
style
});
text.position.set(100, 100);
app.stage.addChild(text);
// Update text
text.text = 'New text';
// Adjust resolution
text.resolution = 2; // Higher = sharper but more memoryBitmapText
High-performance text for dynamic content:
import { BitmapText } from 'pixi.js';
// Requires bitmap font asset
const bitmapText = new BitmapText({
text: 'Score: 0',
style: {
fontFamily: 'MyBitmapFont',
fontSize: 24,
tint: 0xff0000
}
});
app.stage.addChild(bitmapText);
// Update frequently (very fast)
app.ticker.add(() => {
bitmapText.text = `Score: ${++score}`;
});TextStyle Options
interface TextStyleOptions {
// Font
fontFamily: string | string[];
fontSize: number | string;
fontStyle: 'normal' | 'italic' | 'oblique';
fontWeight: 'normal' | 'bold' | '100-900';
// Fill
fill: number | string | string[] | number[]; // Gradient support
fillGradientStops: number[];
// Stroke
stroke: { color: number | string, width: number, alpha?: number };
// Shadow
dropShadow: {
alpha: number,
angle: number,
blur: number,
color: number | string,
distance: number
};
// Layout
align: 'left' | 'center' | 'right' | 'justify';
wordWrap: boolean;
wordWrapWidth: number;
breakWords: boolean;
lineHeight: number;
letterSpacing: number;
leading: number;
// Other
padding: number;
trim: boolean;
whiteSpace: 'normal' | 'pre' | 'pre-line';
}---
Assets
Asset loading and management system.
Loading Assets
import { Assets } from 'pixi.js';
// Load single asset
const texture = await Assets.load('image.png');
const spritesheet = await Assets.load('spritesheet.json');
// Load multiple assets
const assets = await Assets.load([
'image1.png',
'image2.png',
'sound.mp3'
]);
// Load with aliases
await Assets.add({ alias: 'hero', src: 'hero.png' });
const heroTexture = await Assets.load('hero');
// Load bundle
Assets.addBundle('game', {
player: 'player.png',
enemy: 'enemy.png',
background: 'bg.jpg'
});
const bundle = await Assets.loadBundle('game');
// Access loaded assets
const playerTexture = Assets.get('player');Progress Tracking
Assets.load('large-file.png', (progress) => {
console.log(`Loading: ${Math.round(progress * 100)}%`);
});
// Or with promises
const promise = Assets.load(['file1.png', 'file2.png']);
promise.progress = (progress) => {
console.log(`Progress: ${progress * 100}%`);
};
await promise;Background Loading
// Load in background (non-blocking)
Assets.backgroundLoad(['asset1.png', 'asset2.png']);
// Check if loaded
if (Assets.cache.has('asset1.png')) {
const texture = Assets.get('asset1.png');
}Unloading Assets
// Unload single asset
await Assets.unload('image.png');
// Unload bundle
await Assets.unloadBundle('game');
// Clear cache
Assets.reset();---
Renderer
Low-level rendering system (WebGL/WebGPU).
Properties
renderer.type: string // 'webgl' | 'webgpu'
renderer.width: number
renderer.height: number
renderer.resolution: number
renderer.backgroundColor: number
renderer.backgroundAlpha: numberMethods
// Manual rendering
renderer.render(container);
// Resize
renderer.resize(width, height);
// Clear
renderer.clear();
// Generate texture from display object
const texture = renderer.generateTexture(displayObject, {
resolution: 1,
frame: new Rectangle(0, 0, 100, 100)
});
// Destroy
renderer.destroy();---
DisplayObject
Base class for all renderable objects (Sprite, Graphics, Container, etc.).
Transform Properties
displayObject.position: ObservablePoint // x, y
displayObject.scale: ObservablePoint // x, y scale
displayObject.rotation: number // Radians
displayObject.pivot: ObservablePoint // Rotation pivot
displayObject.skew: ObservablePoint // x, y skew
displayObject.angle: number // Degrees (converts to rotation)Visibility
displayObject.alpha: number // 0-1 opacity
displayObject.visible: boolean // Show/hide
displayObject.renderable: boolean // Render flag
displayObject.cullable: boolean // Viewport culling
displayObject.mask: Graphics | Sprite // MaskingHierarchy
displayObject.parent: Container
displayObject.children: DisplayObject[] // If Container
displayObject.zIndex: number // Render order (if sortableChildren enabled)
displayObject.removeFromParent()
displayObject.destroy(options)Bounds
displayObject.getBounds() // Global bounds
displayObject.getLocalBounds() // Local bounds
displayObject.width: number // Bounding width
displayObject.height: number // Bounding heightInteraction
displayObject.eventMode: string // 'none' | 'passive' | 'static' | 'dynamic'
displayObject.cursor: string // CSS cursor
displayObject.hitArea: Shape // Custom hit detection area
displayObject.interactive: boolean // Enable events (deprecated, use eventMode)---
Events
Interactive event system.
Event Modes
sprite.eventMode = 'static'; // Enable interaction
sprite.eventMode = 'dynamic'; // Enable + propagate to children
sprite.eventMode = 'passive'; // Receive events but don't block
sprite.eventMode = 'none'; // No interaction (default)Mouse Events
sprite.on('pointerdown', (event) => {
console.log('Clicked at:', event.global.x, event.global.y);
});
sprite.on('pointerup', handler);
sprite.on('pointermove', handler);
sprite.on('pointerover', handler); // Mouse enter
sprite.on('pointerout', handler); // Mouse leave
sprite.on('pointerupoutside', handler); // Released outside
// Once
sprite.once('pointerdown', handler);
// Remove
sprite.off('pointerdown', handler);
sprite.removeAllListeners();Touch Events
sprite.on('touchstart', handler);
sprite.on('touchend', handler);
sprite.on('touchmove', handler);
sprite.on('tap', handler);Event Object
interface FederatedPointerEvent {
global: Point; // Global coordinates
client: Point; // Client coordinates
screen: Point; // Screen coordinates
movement: Point; // Delta movement
page: Point; // Page coordinates
button: number; // Mouse button (0=left, 1=middle, 2=right)
buttons: number; // Bitmask of pressed buttons
target: DisplayObject; // Event target
currentTarget: DisplayObject;
type: string; // Event type
preventDefault(): void;
stopPropagation(): void;
}Custom Cursor
sprite.cursor = 'pointer';
sprite.cursor = 'grab';
sprite.cursor = 'help';Hit Area
import { Rectangle, Circle, Polygon } from 'pixi.js';
// Rectangle hit area
sprite.hitArea = new Rectangle(0, 0, 100, 100);
// Circle hit area
sprite.hitArea = new Circle(50, 50, 30);
// Polygon hit area
sprite.hitArea = new Polygon([0,0, 100,0, 100,100, 0,100]);---
Utility Classes
Rectangle
const rect = new Rectangle(x, y, width, height);
rect.contains(x, y);
rect.intersects(otherRect);Circle
const circle = new Circle(x, y, radius);
circle.contains(x, y);Point
const point = new Point(x, y);
point.set(x, y);
point.clone();
point.equals(otherPoint);ObservablePoint
const observable = new ObservablePoint(callback, scope);
observable.set(x, y);
observable.x = 100; // Triggers callback---
Performance APIs
CacheAsBitmap
// Convert to texture for faster rendering
displayObject.cacheAsBitmap = true;
// Disable when updating frequently
displayObject.cacheAsBitmap = false;Ticker
import { Ticker } from 'pixi.js';
const ticker = Ticker.shared;
ticker.add((delta) => {
// Update logic
// delta = time since last frame
});
ticker.speed = 0.5; // Half speed
ticker.maxFPS = 30; // Cap at 30 FPS
ticker.minFPS = 10; // Min for deltaTime calculation
ticker.stop();
ticker.start();---
Constants
Blend Modes
import { BLEND_MODES } from 'pixi.js';
sprite.blendMode = BLEND_MODES.NORMAL;
sprite.blendMode = BLEND_MODES.ADD;
sprite.blendMode = BLEND_MODES.MULTIPLY;
sprite.blendMode = BLEND_MODES.SCREEN;
sprite.blendMode = BLEND_MODES.OVERLAY;
sprite.blendMode = BLEND_MODES.DARKEN;
sprite.blendMode = BLEND_MODES.LIGHTEN;
sprite.blendMode = BLEND_MODES.COLOR_DODGE;
sprite.blendMode = BLEND_MODES.COLOR_BURN;
sprite.blendMode = BLEND_MODES.HARD_LIGHT;
sprite.blendMode = BLEND_MODES.SOFT_LIGHT;
sprite.blendMode = BLEND_MODES.DIFFERENCE;
sprite.blendMode = BLEND_MODES.EXCLUSION;
sprite.blendMode = BLEND_MODES.HUE;
sprite.blendMode = BLEND_MODES.SATURATION;
sprite.blendMode = BLEND_MODES.COLOR;
sprite.blendMode = BLEND_MODES.LUMINOSITY;Scale Modes
import { SCALE_MODES } from 'pixi.js';
texture.baseTexture.scaleMode = SCALE_MODES.LINEAR; // Smooth (default)
texture.baseTexture.scaleMode = SCALE_MODES.NEAREST; // Pixelated---
TypeScript Support
PixiJS is written in TypeScript and provides full type definitions.
import { Application, Sprite, Texture, Container } from 'pixi.js';
const app: Application = new Application();
const sprite: Sprite = new Sprite(Texture.WHITE);
const container: Container = new Container();---
Official API Documentation
- Main Docs: https://pixijs.download/release/docs/
- Examples: https://pixijs.io/examples/
- GitHub: https://github.com/pixijs/pixijs
---
This API reference covers PixiJS v8+ core functionality. For advanced features, plugins, and detailed shader programming, consult the official documentation.
PixiJS Filters & Visual Effects Guide
Comprehensive guide to using and creating visual effects with PixiJS filters and shaders.
---
Table of Contents
1. Filter Basics 2. Built-in Filters 3. Custom Filters 4. Shader Programming 5. Effect Combinations 6. Performance Tips
---
Filter Basics
Applying Filters
Filters are WebGL/WebGPU shader programs applied to display objects after rendering.
import { BlurFilter, Sprite } from 'pixi.js';
const sprite = Sprite.from('image.png');
// Single filter
sprite.filters = [new BlurFilter()];
// Multiple filters (applied in order)
sprite.filters = [
new BlurFilter({ strength: 4 }),
new ColorMatrixFilter()
];
// Remove filters
sprite.filters = null;Filter Area Optimization
import { Rectangle } from 'pixi.js';
// Specify filter bounds for performance
sprite.filterArea = new Rectangle(0, 0, sprite.width, sprite.height);
// PixiJS won't need to measure bounds at runtimeFilter on Containers
import { Container } from 'pixi.js';
const container = new Container();
container.addChild(sprite1, sprite2, sprite3);
// Filter applied to entire container
container.filters = [new BlurFilter()];---
Built-in Filters
BlurFilter
Gaussian blur effect for depth of field, motion blur, or soft focus.
import { BlurFilter } from 'pixi.js';
const blur = new BlurFilter({
strength: 8, // Blur radius (default: 8)
quality: 4, // Number of passes (1-5, default: 4)
kernelSize: 5 // Sample size: 5, 7, 9, 11, 13, 15 (default: 5)
});
sprite.filters = [blur];
// Adjust blur dynamically
app.ticker.add(() => {
blur.blur = 5 + Math.sin(Date.now() * 0.001) * 5; // Pulsing blur
});Use Cases:
- Depth of field effects
- Focus/unfocus transitions
- Motion blur
- Background blur (foreground sharp)
Performance: Higher quality and kernelSize = slower. Use lower values for real-time effects.
---
ColorMatrixFilter
Transform colors using matrix multiplication. Includes preset effects.
import { ColorMatrixFilter } from 'pixi.js';
const colorMatrix = new ColorMatrixFilter();
// Grayscale
colorMatrix.greyscale(0.5); // 0 = color, 1 = full grayscale
// Sepia tone
colorMatrix.sepia();
// Black and white
colorMatrix.blackAndWhite();
// Adjust contrast
colorMatrix.contrast(1.5); // >1 = more contrast
// Adjust saturation
colorMatrix.saturate(0.5); // <1 = desaturate, >1 = supersaturate
// Adjust brightness
colorMatrix.brightness(1.2); // >1 = brighter
// Hue rotation
colorMatrix.hue(45); // Rotate hue in degrees
// Negative (invert)
colorMatrix.negative();
// Vintage film effects
colorMatrix.kodachrome();
colorMatrix.technicolor();
colorMatrix.polaroid();
colorMatrix.vintage();
sprite.filters = [colorMatrix];Chaining Effects:
colorMatrix.greyscale(0.3).contrast(1.2).brightness(1.1);Custom Color Matrix:
// 5x4 color matrix [R, G, B, A, offset]
const matrix = [
1, 0, 0, 0, 0, // Red
0, 1, 0, 0, 0, // Green
0, 0, 1, 0, 0, // Blue
0, 0, 0, 1, 0 // Alpha
];
colorMatrix.matrix = matrix;Use Cases:
- Photo filters (Instagram-style)
- Color grading
- Night vision effect
- Damage/flash effects
---
DisplacementFilter
Warp/distort pixels based on a displacement map texture.
import { DisplacementFilter, Sprite } from 'pixi.js';
// Create displacement sprite (usually perlin noise or cloud texture)
const displacementSprite = Sprite.from('displacement.jpg');
displacementSprite.texture.baseTexture.wrapMode = WRAP_MODES.REPEAT;
const displacementFilter = new DisplacementFilter({
sprite: displacementSprite,
scale: 50 // Displacement amount
});
sprite.filters = [displacementFilter];
app.stage.addChild(displacementSprite);
// Animate displacement
app.ticker.add(() => {
displacementSprite.x += 1;
displacementSprite.y += 0.5;
});Parameters:
sprite: Displacement map (red channel = X offset, green channel = Y offset)scale: Displacement intensity (default: 20)
Use Cases:
- Water ripple effects
- Heat distortion
- Portal effects
- Liquid/jelly animations
- Flag waving
Creating Displacement Maps:
// Generate noise texture
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
const ctx = canvas.getContext('2d');
// Draw perlin noise or clouds
// ... (use simplex-noise library or draw manually)
const displacementTexture = Texture.from(canvas);
const displacementSprite = new Sprite(displacementTexture);---
AlphaFilter
Flatten alpha across all children in a container.
import { AlphaFilter, Container } from 'pixi.js';
const container = new Container();
container.addChild(sprite1, sprite2, sprite3);
const alphaFilter = new AlphaFilter(0.5); // 50% opacity
container.filters = [alphaFilter];
// Without filter: each sprite has individual alpha
// With filter: entire container rendered at 50% alphaUse Cases:
- Fade entire UI panel
- Composite transparency
- Layer blending
---
NoiseFilter
Add random grain/noise for film grain or static effects.
import { NoiseFilter } from 'pixi.js';
const noise = new NoiseFilter({
noise: 0.5, // Amount (0-1, default: 0.5)
seed: Math.random() // Random seed
});
sprite.filters = [noise];
// Animated noise
app.ticker.add(() => {
noise.seed = Math.random();
});Use Cases:
- Film grain
- Old TV static
- Analog distortion
- Glitch effects
---
FXAAFilter
Fast approximate anti-aliasing for smooth edges.
import { FXAAFilter } from 'pixi.js';
const fxaa = new FXAAFilter();
sprite.filters = [fxaa];Use Cases:
- Smooth jagged edges
- Improve visual quality on low-res displays
- Reduce aliasing artifacts
---
Custom Filters
Creating a Custom Filter
Custom filters use GLSL shaders for GPU-accelerated effects.
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 waveFilter = new Filter({
glProgram: new GlProgram({ vertex, fragment }),
resources: {
timeUniforms: {
uTime: { value: 0.0, type: 'f32' }
}
}
});
sprite.filters = [waveFilter];
// Update uniform
app.ticker.add((ticker) => {
waveFilter.resources.timeUniforms.uniforms.uTime += 0.04 * ticker.deltaTime;
});---
Example: Pixelate Filter
const pixelateVertex = `
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 pixelateFragment = `
in vec2 vTextureCoord;
uniform sampler2D uTexture;
uniform vec2 uSize;
uniform float uPixelSize;
void main() {
vec2 coord = vTextureCoord * uSize;
vec2 pixelCoord = floor(coord / uPixelSize) * uPixelSize;
vec2 pixelUV = pixelCoord / uSize;
gl_FragColor = texture(uTexture, pixelUV);
}
`;
class PixelateFilter extends Filter {
constructor(pixelSize = 10) {
super({
glProgram: new GlProgram({
vertex: pixelateVertex,
fragment: pixelateFragment
}),
resources: {
pixelateUniforms: {
uSize: { value: new Float32Array([800, 600]), type: 'vec2<f32>' },
uPixelSize: { value: pixelSize, type: 'f32' }
}
}
});
}
get pixelSize() {
return this.resources.pixelateUniforms.uniforms.uPixelSize;
}
set pixelSize(value) {
this.resources.pixelateUniforms.uniforms.uPixelSize = value;
}
}
// Usage
const pixelate = new PixelateFilter(5);
sprite.filters = [pixelate];
// Animate
app.ticker.add(() => {
pixelate.pixelSize = 5 + Math.sin(Date.now() * 0.001) * 4;
});---
Example: Chromatic Aberration
const chromaticFragment = `
in vec2 vTextureCoord;
uniform sampler2D uTexture;
uniform float uAmount;
void main() {
vec2 uv = vTextureCoord;
// Offset RGB channels
float r = texture(uTexture, uv + vec2(uAmount, 0.0)).r;
float g = texture(uTexture, uv).g;
float b = texture(uTexture, uv - vec2(uAmount, 0.0)).b;
gl_FragColor = vec4(r, g, b, 1.0);
}
`;
class ChromaticAberrationFilter extends Filter {
constructor(amount = 0.005) {
super({
glProgram: new GlProgram({
vertex: defaultVertex, // Use default vertex shader
fragment: chromaticFragment
}),
resources: {
chromaticUniforms: {
uAmount: { value: amount, type: 'f32' }
}
}
});
}
get amount() {
return this.resources.chromaticUniforms.uniforms.uAmount;
}
set amount(value) {
this.resources.chromaticUniforms.uniforms.uAmount = value;
}
}
// Usage
const chromatic = new ChromaticAberrationFilter(0.01);
sprite.filters = [chromatic];---
Example: Vignette Filter
const vignetteFragment = `
in vec2 vTextureCoord;
uniform sampler2D uTexture;
uniform float uIntensity;
uniform float uSoftness;
void main() {
vec4 color = texture(uTexture, vTextureCoord);
vec2 uv = vTextureCoord - 0.5;
float dist = length(uv);
float vignette = smoothstep(uIntensity, uIntensity - uSoftness, dist);
gl_FragColor = vec4(color.rgb * vignette, color.a);
}
`;
class VignetteFilter extends Filter {
constructor(intensity = 0.5, softness = 0.3) {
super({
glProgram: new GlProgram({
vertex: defaultVertex,
fragment: vignetteFragment
}),
resources: {
vignetteUniforms: {
uIntensity: { value: intensity, type: 'f32' },
uSoftness: { value: softness, type: 'f32' }
}
}
});
}
get intensity() {
return this.resources.vignetteUniforms.uniforms.uIntensity;
}
set intensity(value) {
this.resources.vignetteUniforms.uniforms.uIntensity = value;
}
get softness() {
return this.resources.vignetteUniforms.uniforms.uSoftness;
}
set softness(value) {
this.resources.vignetteUniforms.uniforms.uSoftness = value;
}
}---
Shader Programming
GLSL Basics
Data Types:
float x = 1.0;
vec2 position = vec2(0.5, 0.5);
vec3 color = vec3(1.0, 0.0, 0.0); // RGB
vec4 rgba = vec4(1.0, 0.0, 0.0, 1.0); // RGBA
sampler2D texture; // Texture samplerBuilt-in Functions:
// Math
sin(x), cos(x), tan(x)
abs(x), sign(x)
floor(x), ceil(x), fract(x)
min(a, b), max(a, b), clamp(x, min, max)
mix(a, b, t) // Linear interpolation
smoothstep(edge0, edge1, x) // Smooth interpolation
// Vector
length(v) // Vector length
distance(a, b) // Distance between vectors
dot(a, b) // Dot product
normalize(v) // Unit vector
// Texture sampling
texture(sampler, uv) // Sample texture at UV coordinatesVertex Shader Template:
in vec2 aPosition;
out vec2 vTextureCoord;
uniform vec4 uInputSize;
uniform vec4 uOutputFrame;
uniform vec4 uOutputTexture;
void main() {
gl_Position = filterVertexPosition();
vTextureCoord = filterTextureCoord();
}Fragment Shader Template:
in vec2 vTextureCoord;
uniform sampler2D uTexture;
void main() {
vec4 color = texture(uTexture, vTextureCoord);
// Modify color
color.rgb *= 0.5; // Darken
gl_FragColor = color;
}---
Uniforms
Pass data from JavaScript to shaders.
const filter = new Filter({
glProgram: new GlProgram({ vertex, fragment }),
resources: {
customUniforms: {
uTime: { value: 0.0, type: 'f32' },
uColor: { value: [1.0, 0.0, 0.0], type: 'vec3<f32>' },
uPosition: { value: new Float32Array([0.5, 0.5]), type: 'vec2<f32>' },
uTexture2: { value: secondTexture, type: 'sampler2D' }
}
}
});
// Access uniforms
filter.resources.customUniforms.uniforms.uTime = 5.0;
filter.resources.customUniforms.uniforms.uColor = [0.0, 1.0, 0.0];In Shader:
uniform float uTime;
uniform vec3 uColor;
uniform vec2 uPosition;
uniform sampler2D uTexture2;
void main() {
// Use uniforms
float wave = sin(vTextureCoord.y * 10.0 + uTime);
vec4 color = texture(uTexture, vTextureCoord) * vec4(uColor, 1.0);
gl_FragColor = color;
}---
Multi-Pass Filters
Apply multiple shader passes for complex effects.
class MultiPassFilter extends Filter {
constructor() {
// First pass: Blur horizontal
const pass1 = new Filter({
glProgram: new GlProgram({ vertex: defaultVertex, fragment: blurHorizontalFragment })
});
// Second pass: Blur vertical
const pass2 = new Filter({
glProgram: new GlProgram({ vertex: defaultVertex, fragment: blurVerticalFragment })
});
// Combine passes
super({
glProgram: new GlProgram({ vertex: defaultVertex, fragment: combineFragment }),
resources: {
pass1Texture: { value: null, type: 'sampler2D' },
pass2Texture: { value: null, type: 'sampler2D' }
}
});
}
}---
Effect Combinations
Glow Effect
Blur + Additive Blend
import { BlurFilter, BLEND_MODES } from 'pixi.js';
// Original sprite
const sprite = Sprite.from('star.png');
// Glow sprite (blurred copy)
const glowSprite = new Sprite(sprite.texture);
glowSprite.filters = [new BlurFilter({ strength: 15 })];
glowSprite.blendMode = BLEND_MODES.ADD;
glowSprite.alpha = 0.8;
const container = new Container();
container.addChild(glowSprite, sprite); // Glow behind
app.stage.addChild(container);---
Outline Effect
Multiple displacement passes
const outlineFragment = `
in vec2 vTextureCoord;
uniform sampler2D uTexture;
uniform float uThickness;
uniform vec3 uColor;
void main() {
vec4 color = texture(uTexture, vTextureCoord);
float alpha = color.a;
// Sample neighboring pixels
alpha += texture(uTexture, vTextureCoord + vec2(uThickness, 0.0)).a;
alpha += texture(uTexture, vTextureCoord - vec2(uThickness, 0.0)).a;
alpha += texture(uTexture, vTextureCoord + vec2(0.0, uThickness)).a;
alpha += texture(uTexture, vTextureCoord - vec2(0.0, uThickness)).a;
// Create outline
float outline = step(0.1, alpha) * (1.0 - color.a);
vec3 finalColor = mix(color.rgb, uColor, outline);
float finalAlpha = max(color.a, outline);
gl_FragColor = vec4(finalColor, finalAlpha);
}
`;
class OutlineFilter extends Filter {
constructor(thickness = 0.01, color = [1, 1, 1]) {
super({
glProgram: new GlProgram({ vertex: defaultVertex, fragment: outlineFragment }),
resources: {
outlineUniforms: {
uThickness: { value: thickness, type: 'f32' },
uColor: { value: color, type: 'vec3<f32>' }
}
}
});
}
}---
CRT Monitor Effect
Scanlines + chromatic aberration + curve
const crtFragment = `
in vec2 vTextureCoord;
uniform sampler2D uTexture;
uniform float uTime;
void main() {
vec2 uv = vTextureCoord;
// Curve screen
uv = uv * 2.0 - 1.0;
uv *= 1.0 + 0.1 * dot(uv, uv);
uv = (uv + 1.0) * 0.5;
// Chromatic aberration
float r = texture(uTexture, uv + vec2(0.002, 0.0)).r;
float g = texture(uTexture, uv).g;
float b = texture(uTexture, uv - vec2(0.002, 0.0)).b;
// Scanlines
float scanline = sin(uv.y * 800.0) * 0.1 + 0.9;
// Flicker
float flicker = sin(uTime * 50.0) * 0.02 + 0.98;
vec3 color = vec3(r, g, b) * scanline * flicker;
gl_FragColor = vec4(color, 1.0);
}
`;---
Film Grain + Vignette
sprite.filters = [
new NoiseFilter({ noise: 0.2 }),
new VignetteFilter(0.5, 0.3),
new ColorMatrixFilter().sepia()
];---
Performance Tips
1. Minimize Filter Count
// ❌ BAD: Too many filters
sprite.filters = [blur1, blur2, colorMatrix, noise, vignette];
// ✅ GOOD: Combine into single custom filter
sprite.filters = [combinedFilter];---
2. Set Filter Area
sprite.filters = [blurFilter];
sprite.filterArea = new Rectangle(0, 0, sprite.width, sprite.height);---
3. Bake Static Filters
// Apply filter once, generate texture
const filteredTexture = renderer.filters.generateFilteredTexture({
texture: originalTexture,
filters: [blurFilter, colorMatrix]
});
const sprite = new Sprite(filteredTexture);
// No runtime filter cost---
4. Use Lower Quality
const blur = new BlurFilter({
strength: 8,
quality: 2, // Lower = faster (1-5)
kernelSize: 5 // Smaller = faster
});---
5. Toggle Filters Based on Performance
let filtersEnabled = true;
app.ticker.add(() => {
const fps = Math.round(1000 / app.ticker.deltaMS);
if (fps < 30 && filtersEnabled) {
sprite.filters = null; // Disable filters
filtersEnabled = false;
} else if (fps > 55 && !filtersEnabled) {
sprite.filters = [blurFilter]; // Re-enable
filtersEnabled = true;
}
});---
Filter Examples Library
Glass/Frosted Effect
sprite.filters = [
new BlurFilter({ strength: 10 }),
new ColorMatrixFilter().brightness(1.2)
];
sprite.alpha = 0.8;Night Vision
const nightVision = new ColorMatrixFilter();
nightVision.greyscale(1);
nightVision.contrast(1.5);
nightVision.brightness(1.5);
sprite.filters = [nightVision];
sprite.tint = 0x00ff00; // Green tintX-Ray
const xray = new ColorMatrixFilter();
xray.negative();
xray.contrast(2);
sprite.filters = [xray];Underwater
sprite.filters = [
new DisplacementFilter({ sprite: waveSprite, scale: 20 }),
new ColorMatrixFilter().saturate(0.7)
];
sprite.tint = 0x88ccff;---
This guide provides comprehensive coverage of PixiJS filters, from built-in options to custom shader programming for advanced visual effects.
PixiJS Performance Optimization Guide
Comprehensive guide to optimizing PixiJS applications for maximum performance and smooth 60 FPS rendering.
---
Table of Contents
1. Performance Profiling 2. Rendering Optimization 3. Texture Management 4. Container Optimization 5. Filter Performance 6. Text Rendering 7. Memory Management 8. Mobile Optimization 9. Advanced Techniques
---
Performance Profiling
Built-in Stats
import { Application } from 'pixi.js';
const app = new Application();
await app.init({ width: 800, height: 600 });
// Access stats
app.ticker.add(() => {
const stats = app.renderer.stats;
console.log('FPS:', Math.round(1000 / app.ticker.deltaMS));
console.log('Draw calls:', stats.drawCalls);
console.log('Texture bind count:', stats.textureCount);
console.log('Shader bind count:', stats.shaderCount);
});Custom Performance Monitor
class PerformanceMonitor {
constructor(app) {
this.app = app;
this.frameCount = 0;
this.lastTime = performance.now();
this.fps = 60;
this.drawCalls = 0;
this.createDisplay();
this.app.ticker.add(this.update.bind(this));
}
createDisplay() {
this.container = document.createElement('div');
this.container.style.cssText = `
position: fixed;
top: 10px;
right: 10px;
background: rgba(0,0,0,0.8);
color: #0f0;
padding: 10px;
font-family: monospace;
font-size: 12px;
z-index: 10000;
`;
document.body.appendChild(this.container);
}
update() {
this.frameCount++;
const now = performance.now();
if (now - this.lastTime >= 1000) {
this.fps = Math.round(this.frameCount * 1000 / (now - this.lastTime));
this.frameCount = 0;
this.lastTime = now;
const stats = this.app.renderer.stats;
this.drawCalls = stats.drawCalls.total;
this.render();
}
}
render() {
const color = this.fps >= 55 ? '#0f0' : this.fps >= 30 ? '#ff0' : '#f00';
this.container.style.color = color;
this.container.innerHTML = `
FPS: ${this.fps}<br>
Draw Calls: ${this.drawCalls}<br>
Sprites: ${this.countSprites()}<br>
Memory: ${this.getMemoryUsage()}
`;
}
countSprites() {
let count = 0;
const traverse = (container) => {
if (container.children) {
container.children.forEach(child => {
count++;
traverse(child);
});
}
};
traverse(this.app.stage);
return count;
}
getMemoryUsage() {
if (performance.memory) {
const mb = (performance.memory.usedJSHeapSize / 1048576).toFixed(2);
return `${mb} MB`;
}
return 'N/A';
}
}
// Usage
const monitor = new PerformanceMonitor(app);---
Rendering Optimization
1. Use ParticleContainer for Large Sprite Counts
Problem: Rendering 1,000+ sprites with regular Container is slow.
Solution: Use ParticleContainer with static properties.
import { ParticleContainer, Particle, Texture } from 'pixi.js';
// ❌ BAD: Regular container (slow)
const container = new Container();
for (let i = 0; i < 10000; i++) {
const sprite = new Sprite(texture);
sprite.x = Math.random() * 800;
sprite.y = Math.random() * 600;
container.addChild(sprite);
}
// ✅ GOOD: ParticleContainer (10x faster)
const particles = new ParticleContainer({
maxSize: 10000,
dynamicProperties: {
position: true, // Only if you need to update positions
scale: false, // Static scale
rotation: false, // Static rotation
color: false // Static color
}
});
for (let i = 0; i < 10000; i++) {
const particle = new Particle({
texture,
x: Math.random() * 800,
y: Math.random() * 600
});
particles.addParticle(particle);
}
app.stage.addChild(particles);Performance Gain: Up to 10x faster rendering for static properties.
---
2. Minimize Draw Calls
Problem: Each texture/shader switch triggers a new draw call.
Solution: Batch sprites with the same texture and blend mode.
// ❌ BAD: Different textures interspersed
const sprites = [];
for (let i = 0; i < 100; i++) {
const tex = i % 2 === 0 ? texture1 : texture2;
sprites.push(new Sprite(tex));
}
// ✅ GOOD: Group by texture
const group1 = new Container();
const group2 = new Container();
for (let i = 0; i < 50; i++) {
group1.addChild(new Sprite(texture1));
group2.addChild(new Sprite(texture2));
}
app.stage.addChild(group1, group2);Tip: Use sprite atlases (texture packing) to combine multiple images into one texture.
---
3. Enable Culling for Off-Screen Objects
Problem: Rendering objects outside viewport wastes GPU cycles.
Solution: Enable viewport culling.
import { Application } from 'pixi.js';
const app = new Application();
await app.init({
width: 800,
height: 600,
cullable: true // Enable automatic culling
});
// Or per-object
sprite.cullable = true;
// Manual culling
app.ticker.add(() => {
const bounds = app.screen;
sprites.forEach(sprite => {
const spriteBounds = sprite.getBounds();
// Check if sprite is in viewport
sprite.renderable = (
spriteBounds.x < bounds.width &&
spriteBounds.x + spriteBounds.width > 0 &&
spriteBounds.y < bounds.height &&
spriteBounds.y + spriteBounds.height > 0
);
});
});CullerPlugin:
// Automatic viewport culling plugin
import { CullerPlugin } from 'pixi.js';
// Enabled by default in Application
app.cullable = true;Performance Gain: Up to 50% for scenes with many off-screen objects.
---
4. Cache Static Graphics as Bitmaps
Problem: Complex vector graphics re-render every frame.
Solution: Convert to texture using cacheAsBitmap.
import { Graphics } from 'pixi.js';
const complexShape = new Graphics();
// Draw many shapes
for (let i = 0; i < 100; i++) {
complexShape.circle(
Math.random() * 200,
Math.random() * 200,
Math.random() * 10
).fill(Math.random() * 0xffffff);
}
// ✅ Cache as bitmap for static graphics
complexShape.cacheAsBitmap = true;
// ❌ Don't use for frequently changing graphics
// complexShape.cacheAsBitmap = false; // If updating oftenWhen to Use:
- ✅ Static UI elements
- ✅ Backgrounds
- ✅ Complex shapes that don't change
- ❌ Animated graphics
- ❌ Frequently updated elements
---
5. Reduce Resolution on Low-End Devices
Problem: High-resolution rendering on mobile drains battery and causes lag.
Solution: Adjust resolution based on device capabilities.
const isMobile = /Mobi|Android/i.test(navigator.userAgent);
const resolution = isMobile ? 1 : window.devicePixelRatio || 1;
const app = new Application();
await app.init({
width: 800,
height: 600,
resolution,
autoDensity: true
});
// Dynamic resolution scaling
function adjustResolution() {
const fps = Math.round(1000 / app.ticker.deltaMS);
if (fps < 30 && app.renderer.resolution > 1) {
app.renderer.resolution *= 0.9;
} else if (fps > 55 && app.renderer.resolution < window.devicePixelRatio) {
app.renderer.resolution = Math.min(app.renderer.resolution * 1.1, window.devicePixelRatio);
}
}
app.ticker.add(adjustResolution);---
Texture Management
1. Destroy Unused Textures
Problem: Textures consume GPU memory even when not displayed.
Solution: Explicitly destroy textures when done.
import { Texture } from 'pixi.js';
const texture = Texture.from('image.png');
const sprite = new Sprite(texture);
// When done
sprite.destroy({ texture: true, baseTexture: true });
// Or destroy texture directly
texture.destroy(true); // true = also destroy baseTextureBatch Destruction with Delay:
// Prevent frame drops by staggering destruction
const textures = [tex1, tex2, tex3, tex4];
textures.forEach((tex, index) => {
setTimeout(() => {
tex.destroy(true);
}, index * 50 + Math.random() * 50);
});---
2. Use Texture Atlases (Sprite Sheets)
Problem: Loading many individual images causes numerous HTTP requests and draw calls.
Solution: Pack images into sprite sheets.
import { Assets, Sprite } from 'pixi.js';
// Load sprite sheet
await Assets.load('spritesheet.json');
// Access individual frames
const texture1 = Texture.from('frame1.png');
const texture2 = Texture.from('frame2.png');
const sprite1 = new Sprite(texture1);
const sprite2 = new Sprite(texture2);
// All batched in single draw call
app.stage.addChild(sprite1, sprite2);Tools for Creating Sprite Sheets:
- TexturePacker: https://www.codeandweb.com/texturepacker
- ShoeBox: https://renderhjs.net/shoebox/
- Free Texture Packer: https://free-tex-packer.com/
---
3. Optimize Texture Sizes
Problem: Large textures consume excessive memory.
Solution: Use appropriate sizes and compression.
// ❌ BAD: 4096x4096 texture (64MB RGBA)
const hugeTexture = Texture.from('huge-image-4k.png');
// ✅ GOOD: 1024x1024 texture (4MB RGBA)
const optimizedTexture = Texture.from('optimized-image-1k.png');
// Power-of-2 sizes for best performance
// Good sizes: 256, 512, 1024, 2048
// Avoid odd sizes: 300, 500, 1500
// Use NEAREST for pixel art
texture.baseTexture.scaleMode = SCALE_MODES.NEAREST;
// Use LINEAR for photos
texture.baseTexture.scaleMode = SCALE_MODES.LINEAR;---
4. Lazy Load Assets
Problem: Loading all assets upfront delays game start.
Solution: Load assets on-demand.
import { Assets } from 'pixi.js';
// Preload critical assets
const criticalAssets = await Assets.load([
'ui/background.png',
'ui/logo.png'
]);
// Background load game assets
Assets.backgroundLoad([
'characters/hero.png',
'characters/enemy.png',
'levels/level1.jpg'
]);
// Check if asset is loaded
if (Assets.cache.has('characters/hero.png')) {
const heroTexture = Assets.get('characters/hero.png');
const hero = new Sprite(heroTexture);
}
// Load on-demand
async function showLevel(levelNumber) {
const levelTexture = await Assets.load(`levels/level${levelNumber}.jpg`);
// Use texture
}---
Container Optimization
1. Disable Unnecessary Features
import { Container } from 'pixi.js';
const container = new Container();
// ❌ Don't enable unless needed
container.sortableChildren = false; // Z-index sorting (expensive)
container.interactiveChildren = false; // Child interaction (expensive)
// ✅ Enable only when required
if (needsSorting) {
container.sortableChildren = true;
}---
2. Use Object Pooling
Problem: Creating/destroying objects causes garbage collection pauses.
Solution: Reuse objects via pooling.
class SpritePool {
constructor(texture, initialSize = 100) {
this.texture = texture;
this.available = [];
this.active = [];
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 bulletPool = new SpritePool(bulletTexture, 50);
// Spawn
const bullet = bulletPool.spawn(100, 200);
app.stage.addChild(bullet);
// Despawn
bulletPool.despawn(bullet);Performance Gain: Eliminates GC pauses, smoother frame times.
---
3. Flatten Hierarchy
Problem: Deep nesting requires traversing many containers.
Solution: Keep hierarchy shallow when possible.
// ❌ BAD: Deep nesting
const root = new Container();
const level1 = new Container();
const level2 = new Container();
const level3 = new Container();
root.addChild(level1);
level1.addChild(level2);
level2.addChild(level3);
level3.addChild(sprite);
// ✅ GOOD: Flat structure
const root = new Container();
root.addChild(sprite);
// Use position offsets instead of nested containers
sprite.x = parentX + childX;
sprite.y = parentY + childY;---
Filter Performance
1. Limit Filter Usage
Problem: Filters are expensive WebGL operations.
Solution: Use sparingly, optimize where possible.
import { BlurFilter } from 'pixi.js';
// ❌ BAD: Filter on every sprite
sprites.forEach(sprite => {
sprite.filters = [new BlurFilter()];
});
// ✅ GOOD: Filter on container
const container = new Container();
sprites.forEach(sprite => container.addChild(sprite));
container.filters = [new BlurFilter()];
// ✅ BETTER: Bake filter into texture
const filteredTexture = renderer.filters.generateFilteredTexture({
texture: originalTexture,
filters: [new BlurFilter({ strength: 5 })]
});
const sprite = new Sprite(filteredTexture);---
2. Specify Filter Area
Problem: PixiJS measures filter bounds at runtime (expensive).
Solution: Manually specify filterArea.
import { BlurFilter, Rectangle } from 'pixi.js';
const sprite = new Sprite(texture);
sprite.filters = [new BlurFilter()];
// ✅ Specify filter area for performance
sprite.filterArea = new Rectangle(0, 0, sprite.width, sprite.height);
// Update if sprite resizes
sprite.on('resize', () => {
sprite.filterArea = new Rectangle(0, 0, sprite.width, sprite.height);
});Performance Gain: Avoids runtime bounds calculation.
---
3. Release Filters When Not Needed
// Enable filter
sprite.filters = [new BlurFilter()];
// Disable filter (releases GPU memory)
sprite.filters = null;
// Toggle based on game state
if (gameState === 'paused') {
sprite.filters = [new BlurFilter()];
} else {
sprite.filters = null;
}---
Text Rendering
1. Use BitmapText for Dynamic Text
Problem: Standard Text re-renders texture on every update.
Solution: Use BitmapText for frequently changing text.
import { Text, BitmapText } from 'pixi.js';
// ❌ BAD: Standard Text (expensive updates)
const scoreText = new Text({ text: 'Score: 0' });
app.ticker.add(() => {
scoreText.text = `Score: ${++score}`; // Re-renders texture every frame
});
// ✅ GOOD: BitmapText (much faster)
const scoreBitmap = new BitmapText({
text: 'Score: 0',
style: { fontFamily: 'MyBitmapFont', fontSize: 24 }
});
app.ticker.add(() => {
scoreBitmap.text = `Score: ${++score}`; // Fast glyph updates
});Performance: BitmapText is 10-50x faster for dynamic text.
---
2. Reduce Text Resolution
Problem: High-resolution text consumes memory.
Solution: Lower resolution for less critical text.
import { Text, TextStyle } from 'pixi.js';
const style = new TextStyle({ fontSize: 36 });
const text = new Text({ text: 'Hello', style });
// Default resolution matches renderer (e.g., 2 on Retina)
text.resolution = 1; // Reduce to 1 for memory savings
// Still looks good, uses less memory---
3. Bake Filters into Text
Problem: Runtime filters on text are expensive.
Solution: Apply filters at texture creation.
import { Text, TextStyle, BlurFilter } from 'pixi.js';
const style = new TextStyle({
fontFamily: 'Arial',
fontSize: 36,
fill: '#ffffff',
filters: [new BlurFilter()] // Baked into texture at creation
});
const text = new Text({ text: 'Glowing Text', style });
// Filter applied once at creation, not every frame---
Memory Management
1. Destroy Display Objects Properly
import { Sprite, Container } from 'pixi.js';
const sprite = new Sprite(texture);
const container = new Container();
// ✅ GOOD: Destroy with options
sprite.destroy({
children: true, // Destroy children
texture: false, // Keep texture (if used elsewhere)
baseTexture: false // Keep baseTexture
});
// Destroy container and all children
container.destroy({ children: true });
// Destroy texture when completely done
texture.destroy(true); // true = also destroy baseTexture---
2. Clear Event Listeners
const sprite = new Sprite(texture);
sprite.on('pointerdown', onPointerDown);
sprite.on('pointermove', onPointerMove);
// ✅ Remove listeners before destroying
sprite.off('pointerdown', onPointerDown);
sprite.off('pointermove', onPointerMove);
// Or remove all
sprite.removeAllListeners();
sprite.destroy();---
3. Monitor Memory Usage
function logMemoryUsage() {
if (performance.memory) {
const used = (performance.memory.usedJSHeapSize / 1048576).toFixed(2);
const total = (performance.memory.totalJSHeapSize / 1048576).toFixed(2);
const limit = (performance.memory.jsHeapSizeLimit / 1048576).toFixed(2);
console.log(`Memory: ${used}MB / ${total}MB (Limit: ${limit}MB)`);
}
}
setInterval(logMemoryUsage, 5000);---
Mobile Optimization
1. Disable Anti-Aliasing
const app = new Application();
await app.init({
width: 800,
height: 600,
antialias: false, // Faster on mobile
resolution: 1 // Lower resolution
});---
2. Reduce Particle Count
const isMobile = /Mobi|Android/i.test(navigator.userAgent);
const particleCount = isMobile ? 1000 : 5000;
for (let i = 0; i < particleCount; i++) {
particles.addParticle(new Particle({ texture }));
}---
3. Limit Frame Rate on Battery
import { Ticker } from 'pixi.js';
function checkBattery() {
if ('getBattery' in navigator) {
navigator.getBattery().then(battery => {
if (battery.charging === false && battery.level < 0.2) {
Ticker.shared.maxFPS = 30; // Reduce to 30 FPS
} else {
Ticker.shared.maxFPS = 60;
}
});
}
}
checkBattery();---
Advanced Techniques
1. Use WebWorkers for Heavy Calculations
// worker.js
self.onmessage = function(e) {
const { particles, delta } = e.data;
// Update particle physics
particles.forEach(p => {
p.vx += (Math.random() - 0.5) * 0.1;
p.vy += 0.05;
p.x += p.vx * delta;
p.y += p.vy * delta;
});
self.postMessage(particles);
};
// main.js
const worker = new Worker('worker.js');
worker.onmessage = function(e) {
const updatedParticles = e.data;
// Apply to PixiJS particles
updatedParticles.forEach((data, i) => {
particles.particleChildren[i].x = data.x;
particles.particleChildren[i].y = data.y;
});
};
app.ticker.add((ticker) => {
const particleData = particles.particleChildren.map(p => ({
x: p.x,
y: p.y,
vx: p.vx || 0,
vy: p.vy || 0
}));
worker.postMessage({ particles: particleData, delta: ticker.deltaTime });
});---
2. Implement Spatial Hashing for Collision Detection
class SpatialHash {
constructor(cellSize) {
this.cellSize = cellSize;
this.grid = new Map();
}
clear() {
this.grid.clear();
}
insert(sprite) {
const cells = this.getCells(sprite);
cells.forEach(cell => {
const key = `${cell.x},${cell.y}`;
if (!this.grid.has(key)) {
this.grid.set(key, []);
}
this.grid.get(key).push(sprite);
});
}
getCells(sprite) {
const bounds = sprite.getBounds();
const cells = [];
const minX = Math.floor(bounds.x / this.cellSize);
const maxX = Math.floor((bounds.x + bounds.width) / this.cellSize);
const minY = Math.floor(bounds.y / this.cellSize);
const maxY = Math.floor((bounds.y + bounds.height) / this.cellSize);
for (let x = minX; x <= maxX; x++) {
for (let y = minY; y <= maxY; y++) {
cells.push({ x, y });
}
}
return cells;
}
getNearby(sprite) {
const cells = this.getCells(sprite);
const nearby = new Set();
cells.forEach(cell => {
const key = `${cell.x},${cell.y}`;
const sprites = this.grid.get(key);
if (sprites) {
sprites.forEach(s => {
if (s !== sprite) nearby.add(s);
});
}
});
return Array.from(nearby);
}
}
// Usage
const spatialHash = new SpatialHash(100);
app.ticker.add(() => {
spatialHash.clear();
// Insert all sprites
sprites.forEach(sprite => spatialHash.insert(sprite));
// Check collisions only with nearby sprites
sprites.forEach(sprite => {
const nearby = spatialHash.getNearby(sprite);
nearby.forEach(other => {
if (checkCollision(sprite, other)) {
handleCollision(sprite, other);
}
});
});
});Performance: O(n) instead of O(n²) for collision detection.
---
Performance Checklist
✅ Rendering
- [ ] Use ParticleContainer for 1,000+ sprites
- [ ] Batch sprites by texture
- [ ] Enable culling for off-screen objects
- [ ] Cache static graphics as bitmaps
- [ ] Minimize draw calls
✅ Textures
- [ ] Destroy unused textures
- [ ] Use sprite atlases
- [ ] Optimize texture sizes (power-of-2)
- [ ] Lazy load non-critical assets
✅ Containers
- [ ] Disable sortableChildren unless needed
- [ ] Use object pooling
- [ ] Keep hierarchy shallow
✅ Filters
- [ ] Limit filter usage (1-2 per scene)
- [ ] Specify filterArea
- [ ] Release filters when not needed
- [ ] Bake filters into textures
✅ Text
- [ ] Use BitmapText for dynamic text
- [ ] Reduce text resolution
- [ ] Bake filters into TextStyle
✅ Memory
- [ ] Destroy objects properly
- [ ] Clear event listeners
- [ ] Monitor memory usage
✅ Mobile
- [ ] Disable anti-aliasing
- [ ] Reduce particle counts
- [ ] Lower resolution
- [ ] Limit frame rate on battery
---
Debugging Performance Issues
Identify Bottlenecks
// Measure specific operations
console.time('particleUpdate');
updateParticles();
console.timeEnd('particleUpdate');
// Profile draw calls
console.log('Draw calls:', app.renderer.stats.drawCalls.total);
// Check texture count
console.log('Textures bound:', app.renderer.stats.textureCount);Common Issues
| Symptom | Likely Cause | Solution |
|---|---|---|
| Low FPS | Too many draw calls | Batch sprites, use atlases |
| Stuttering | GC pauses | Use object pooling |
| High memory | Texture leaks | Destroy textures properly |
| Slow filters | Too many filters | Limit usage, bake into textures |
| Laggy text | Text updates | Use BitmapText |
---
This guide provides comprehensive strategies for optimizing PixiJS applications to achieve smooth 60 FPS performance across devices.
Related skills
How it compares
Pick pixijs-2d for WebGL 2D canvas games and effects rather than general React UI or Three.js 3D rendering skills.
FAQ
What does pixijs-2d do?
Build high-performance 2D WebGL games and interactive graphics with PixiJS.
When should I use pixijs-2d?
User builds PixiJS 2D graphics, sprites, or WebGL interactive scenes.
Is pixijs-2d safe to install?
Review the Security Audits panel on this page before installing in production.