pixijs-2d by freshtechbro/claudedesignskills
npx skills add https://github.com/freshtechbro/claudedesignskills --skill pixijs-2d使用 WebGL/WebGPU 创建交互式图形、粒子效果和基于 Canvas 应用程序的快速、轻量级 2D 渲染引擎。
遇到以下情况时触发此技能:
使用 PixiJS 处理:高性能 2D 渲染(高达 100,000+ 个精灵)、粒子系统、交互式 UI、2D 游戏、WebGL 加速的数据可视化。
不适用于:3D 图形(使用 Three.js/R3F)、简单动画(使用 Motion/GSAP)、基本的 DOM 操作。
PixiJS 应用程序的入口点:
import { Application } from 'pixi.js';
const app = new Application();
await app.init({
width: 800,
height: 600,
backgroundColor: 0x1099bb,
antialias: true, // 平滑边缘
resolution: window.devicePixelRatio || 1
});
document.body.appendChild(app.canvas);
关键属性:
app.stage:所有显示对象的根容器广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
app.renderer:WebGL/WebGPU 渲染器实例app.ticker:用于动画的更新循环app.screen:Canvas 尺寸从图像加载的核心视觉元素:
import { Assets, Sprite } from 'pixi.js';
// 加载纹理
const texture = await Assets.load('path/to/image.png');
// 创建精灵
const sprite = new Sprite(texture);
sprite.anchor.set(0.5); // 中心点
sprite.position.set(400, 300);
sprite.scale.set(2); // 2倍缩放
sprite.rotation = Math.PI / 4; // 45度
sprite.alpha = 0.8; // 80% 不透明度
sprite.tint = 0xff0000; // 红色调
app.stage.addChild(sprite);
快速创建:
const sprite = Sprite.from('path/to/image.png');
以编程方式绘制矢量形状:
import { Graphics } from 'pixi.js';
const graphics = new Graphics();
// 矩形
graphics.rect(50, 50, 100, 100).fill('blue');
// 带描边的圆形
graphics.circle(200, 100, 50).fill('red').stroke({ width: 2, color: 'white' });
// 复杂路径
graphics
.moveTo(300, 100)
.lineTo(350, 150)
.lineTo(250, 150)
.closePath()
.fill({ color: 0x00ff00, alpha: 0.5 });
app.stage.addChild(graphics);
SVG 支持:
graphics.svg('<svg><path d="M 100 350 q 150 -300 300 0" /></svg>');
用于渲染数千个精灵的优化容器:
import { ParticleContainer, Particle, Texture } from 'pixi.js';
const texture = Texture.from('particle.png');
const container = new ParticleContainer({
dynamicProperties: {
position: true, // 允许位置更新
scale: false, // 静态缩放
rotation: false, // 静态旋转
color: false // 静态颜色
}
});
// 添加 10,000 个粒子
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);
性能:对于静态属性,比常规 Container 快 10 倍。
使用 WebGL 着色器应用逐像素效果:
import { BlurFilter, DisplacementFilter, ColorMatrixFilter } from 'pixi.js';
// 模糊
const blurFilter = new BlurFilter({ strength: 8, quality: 4 });
sprite.filters = [blurFilter];
// 多个滤镜
sprite.filters = [
new BlurFilter({ strength: 4 }),
new ColorMatrixFilter() // 颜色变换
];
// 为性能设置自定义滤镜区域
sprite.filterArea = new Rectangle(0, 0, 200, 100);
可用滤镜:
BlurFilter:高斯模糊ColorMatrixFilter:颜色变换(棕褐色、灰度等)DisplacementFilter:扭曲/变形像素AlphaFilter:在子元素间展平 AlphaNoiseFilter:随机颗粒效果FXAAFilter:抗锯齿显示带样式的文本:
import { Text, BitmapText, TextStyle } from 'pixi.js';
// 标准文本
const style = new TextStyle({
fontFamily: 'Arial',
fontSize: 36,
fill: '#ffffff',
stroke: { color: '#000000', width: 4 },
filters: [new BlurFilter()] // 在纹理创建时烘焙滤镜
});
const text = new Text({ text: 'Hello PixiJS!', style });
text.position.set(100, 100);
// 位图文本(对于动态文本更快)
const bitmapText = new BitmapText({
text: 'Score: 0',
style: { fontFamily: 'MyBitmapFont', fontSize: 24 }
});
性能提示:对于频繁变化的文本(分数、计数器)使用 BitmapText。
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'; // 启用交互性
bunny.cursor = 'pointer';
// 事件
bunny.on('pointerdown', () => {
bunny.scale.set(1.2);
});
bunny.on('pointerup', () => {
bunny.scale.set(1.0);
});
bunny.on('pointerover', () => {
bunny.tint = 0xff0000; // 悬停时变红
});
bunny.on('pointerout', () => {
bunny.tint = 0xffffff; // 重置
});
app.stage.addChild(bunny);
// 动画循环
app.ticker.add((ticker) => {
bunny.rotation += 0.01 * ticker.deltaTime;
});
import { Graphics, Application } from 'pixi.js';
const app = new Application();
await app.init({ width: 800, height: 600 });
document.body.appendChild(app.canvas);
const graphics = new Graphics();
// 带渐变的矩形
graphics.rect(50, 50, 200, 100).fill({
color: 0x3399ff,
alpha: 0.8
});
// 带描边的圆形
graphics.circle(400, 300, 80)
.fill('yellow')
.stroke({ width: 4, color: 'orange' });
// 星形
graphics.star(600, 300, 5, 50, 0).fill({ color: 0xffdf00, alpha: 0.9 });
// 自定义路径
graphics
.moveTo(100, 400)
.bezierCurveTo(150, 300, 250, 300, 300, 400)
.stroke({ width: 3, color: 'white' });
// 孔洞
graphics
.rect(450, 400, 150, 100).fill('red')
.beginHole()
.circle(525, 450, 30)
.endHole();
app.stage.addChild(graphics);
// 动态绘制(动画)
app.ticker.add(() => {
graphics.clear();
const time = Date.now() * 0.001;
const x = 400 + Math.cos(time) * 100;
const y = 300 + Math.sin(time) * 100;
graphics.circle(x, y, 20).fill('cyan');
});
import { Application, ParticleContainer, Particle, Texture } from 'pixi.js';
const app = new Application();
await app.init({ width: 800, height: 600, backgroundColor: 0x000000 });
document.body.appendChild(app.canvas);
const texture = Texture.from('spark.png');
const particles = new ParticleContainer({
dynamicProperties: {
position: true, // 每帧更新位置
scale: true, // 通过缩放淡出
rotation: true, // 旋转粒子
color: false // 静态颜色
}
});
const particleData = [];
// 创建粒子
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, // 轻微向上偏移
life: 1.0
});
}
app.stage.addChild(particles);
// 更新循环
app.ticker.add((ticker) => {
particleData.forEach(data => {
// 物理
data.particle.x += data.vx * ticker.deltaTime;
data.particle.y += data.vy * ticker.deltaTime;
data.vy += 0.1 * ticker.deltaTime; // 重力
// 淡出
data.life -= 0.01 * ticker.deltaTime;
data.particle.scaleX = data.life * 0.5;
data.particle.scaleY = data.life * 0.5;
// 重置粒子
if (data.life <= 0) {
data.particle.x = 400;
data.particle.y = 300;
data.vx = (Math.random() - 0.5) * 5;
data.vy = (Math.random() - 0.5) * 5 - 2;
data.life = 1.0;
}
});
});
import { Application, Sprite, Assets, BlurFilter, DisplacementFilter } from 'pixi.js';
const app = new Application();
await app.init({ width: 800, height: 600 });
document.body.appendChild(app.canvas);
const texture = await Assets.load('photo.jpg');
const photo = new Sprite(texture);
photo.position.set(100, 100);
// 模糊滤镜
const blurFilter = new BlurFilter({ strength: 5, quality: 4 });
// 置换滤镜(波浪效果)
const displacementTexture = await Assets.load('displacement.jpg');
const displacementSprite = Sprite.from(displacementTexture);
const displacementFilter = new DisplacementFilter({
sprite: displacementSprite,
scale: 50
});
// 应用多个滤镜
photo.filters = [blurFilter, displacementFilter];
// 使用 filterArea 优化
photo.filterArea = new Rectangle(0, 0, photo.width, photo.height);
app.stage.addChild(photo);
// 动画置换
app.ticker.add((ticker) => {
displacementSprite.x += 1 * ticker.deltaTime;
displacementSprite.y += 0.5 * ticker.deltaTime;
});
import { Filter, GlProgram } from 'pixi.js';
const vertex = `
in vec2 aPosition;
out vec2 vTextureCoord;
uniform vec4 uInputSize;
uniform vec4 uOutputFrame;
uniform vec4 uOutputTexture;
vec4 filterVertexPosition() {
vec2 position = aPosition * uOutputFrame.zw + uOutputFrame.xy;
position.x = position.x * (2.0 / uOutputTexture.x) - 1.0;
position.y = position.y * (2.0*uOutputTexture.z / uOutputTexture.y) - uOutputTexture.z;
return vec4(position, 0.0, 1.0);
}
vec2 filterTextureCoord() {
return aPosition * (uOutputFrame.zw * uInputSize.zw);
}
void main() {
gl_Position = filterVertexPosition();
vTextureCoord = filterTextureCoord();
}
`;
const fragment = `
in vec2 vTextureCoord;
uniform sampler2D uTexture;
uniform float uTime;
void main() {
vec2 uv = vTextureCoord;
// 波浪扭曲
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];
// 更新 uniform
app.ticker.add((ticker) => {
customFilter.resources.timeUniforms.uniforms.uTime += 0.04 * ticker.deltaTime;
});
import { Application, Assets, AnimatedSprite } from 'pixi.js';
const app = new Application();
await app.init({ width: 800, height: 600 });
document.body.appendChild(app.canvas);
// 加载精灵表
await Assets.load('spritesheet.json');
// 从帧创建动画
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);
// 控制播放
animation.stop();
animation.gotoAndPlay(0);
animation.onComplete = () => {
console.log('动画完成!');
};
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 = [];
}
}
// 用法
const bulletTexture = Texture.from('bullet.png');
const bulletPool = new SpritePool(bulletTexture, 50);
// 生成子弹
const bullet = bulletPool.spawn(100, 200);
app.stage.addChild(bullet);
// 2秒后回收
setTimeout(() => {
bulletPool.despawn(bullet);
}, 2000);
import { useEffect, useRef } from 'react';
import { Application } from 'pixi.js';
function PixiCanvas() {
const canvasRef = useRef(null);
const appRef = useRef(null);
useEffect(() => {
const init = async () => {
const app = new Application();
await app.init({
width: 800,
height: 600,
backgroundColor: 0x1099bb
});
canvasRef.current.appendChild(app.canvas);
appRef.current = app;
// 设置场景
// ... 添加精灵、图形等
};
init();
return () => {
if (appRef.current) {
appRef.current.destroy(true, { children: true });
}
};
}, []);
return <div ref={canvasRef} />;
}
import * as THREE from 'three';
import { Application, Sprite, Text } from 'pixi.js';
// Three.js 场景
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 叠加层
const pixiApp = new Application();
await pixiApp.init({
width: window.innerWidth,
height: window.innerHeight,
backgroundAlpha: 0 // 透明背景
});
pixiApp.canvas.style.position = 'absolute';
pixiApp.canvas.style.top = '0';
pixiApp.canvas.style.left = '0';
pixiApp.canvas.style.pointerEvents = 'none'; // 点击穿透
document.body.appendChild(pixiApp.canvas);
// 添加 UI 元素
const scoreText = new Text({ text: 'Score: 0', style: { fontSize: 24, fill: 'white' } });
scoreText.position.set(20, 20);
pixiApp.stage.addChild(scoreText);
// 渲染循环
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera); // 3D 场景
pixiApp.renderer.render(pixiApp.stage); // 2D 叠加层
}
animate();
// 不要:常规容器(对于 1000+ 精灵很慢)
const container = new Container();
for (let i = 0; i < 10000; i++) {
container.addChild(new Sprite(texture));
}
// 要:粒子容器(快 10 倍)
const particles = new ParticleContainer({
dynamicProperties: { position: true }
});
for (let i = 0; i < 10000; i++) {
particles.addParticle(new Particle({ texture }));
}
// 设置 filterArea 以避免运行时测量
sprite.filterArea = new Rectangle(0, 0, 200, 100);
// 不需要时释放滤镜
sprite.filters = null;
// 在创建时将滤镜烘焙到文本中
const style = new TextStyle({
filters: [new BlurFilter()] // 在纹理创建时应用一次
});
// 完成后销毁纹理
texture.destroy();
// 分批销毁并延迟以防止帧率下降
textures.forEach((tex, i) => {
setTimeout(() => tex.destroy(), Math.random() * 100);
});
sprite.cullable = true; // 如果在视口外则跳过渲染
// 使用 CullerPlugin
import { CullerPlugin } from 'pixi.js';
// 将复杂图形转换为纹理以加速渲染
const complexShape = new Graphics();
// ... 绘制许多形状
complexShape.cacheAsBitmap = true; // 渲染到纹理一次
const app = new Application();
await app.init({
antialias: false, // 在移动设备上禁用以提高性能
resolution: 1, // 在低端设备上降低分辨率
autoDensity: true
});
// 不要:标准文本(更新开销大)
const text = new Text({ text: `Score: ${score}` });
app.ticker.add(() => {
text.text = `Score: ${++score}`; // 每帧重新渲染纹理
});
// 要:位图文本(快得多)
const bitmapText = new BitmapText({ text: `Score: ${score}` });
app.ticker.add(() => {
bitmapText.text = `Score: ${++score}`;
});
问题:未释放的 GPU 资源导致内存泄漏。
解决方案:
// 始终销毁精灵和纹理
sprite.destroy({ children: true, texture: true, baseTexture: true });
// 销毁滤镜
sprite.filters = null;
// 销毁图形
graphics.destroy();
问题:当 dynamicProperties.scale = false 时更改 scale 无效。
解决方案:
const container = new ParticleContainer({
dynamicProperties: {
position: true,
scale: true, // 如果需要更新则启用
rotation: true,
color: true
}
});
// 如果属性是静态的但你更改了它们,调用 update:
container.update();
问题:滤镜开销大;过多会导致性能问题。
解决方案:
// 限制滤镜使用
sprite.filters = [blurFilter]; // 最多 1-2 个滤镜
// 使用 filterArea 限制处理范围
sprite.filterArea = new Rectangle(0, 0, sprite.width, sprite.height);
// 尽可能将滤镜烘焙到纹理中
const filteredTexture = renderer.filters.generateFilteredTexture({
texture,
filters: [blurFilter]
});
问题:更新文本每次都会重新生成纹理。
解决方案:
// 对频繁变化的文本使用位图文本
const bitmapText = new BitmapText({ text: 'Score: 0' });
// 降低分辨率以减少内存占用
text.resolution = 1; // 低于设备像素比
问题:调用 clear() 会移除所有几何图形但不会自动重绘。
解决方案:
graphics.clear(); // 移除所有形状
// 重绘新形状
graphics.rect(0, 0, 100, 100).fill('blue');
问题:从 URL 创建精灵会导致异步问题。
解决方案:
// 不要:
const sprite = Sprite.from('image.png'); // 可能异步加载
// 要:
const texture = await Assets.load('image.png');
const sprite = new Sprite(texture);
PixiJS 擅长利用 WebGL 加速进行高性能 2D 渲染。主要优势:
适用于性能至关重要的粒子系统、2D 游戏、数据可视化和交互式 Canvas 应用程序。
每周安装次数
137
代码仓库
GitHub 星标数
11
首次出现
2026年2月27日
安全审计
安装于
opencode136
gemini-cli134
codex134
amp134
cline134
github-copilot134
Fast, lightweight 2D rendering engine for creating interactive graphics, particle effects, and canvas-based applications using WebGL/WebGPU.
Trigger this skill when you encounter:
Use PixiJS for : High-performance 2D rendering (up to 100,000+ sprites), particle systems, interactive UI, 2D games, data visualization with WebGL acceleration.
Don't use for : 3D graphics (use Three.js/R3F), simple animations (use Motion/GSAP), basic DOM manipulation.
The entry point for PixiJS applications:
import { Application } from 'pixi.js';
const app = new Application();
await app.init({
width: 800,
height: 600,
backgroundColor: 0x1099bb,
antialias: true, // Smooth edges
resolution: window.devicePixelRatio || 1
});
document.body.appendChild(app.canvas);
Key Properties :
app.stage: Root container for all display objectsapp.renderer: WebGL/WebGPU renderer instanceapp.ticker: Update loop for animationsapp.screen: Canvas dimensionsCore visual elements loaded from images:
import { Assets, Sprite } from 'pixi.js';
// Load texture
const texture = await Assets.load('path/to/image.png');
// Create sprite
const sprite = new Sprite(texture);
sprite.anchor.set(0.5); // Center pivot
sprite.position.set(400, 300);
sprite.scale.set(2); // 2x scale
sprite.rotation = Math.PI / 4; // 45 degrees
sprite.alpha = 0.8; // 80% opacity
sprite.tint = 0xff0000; // Red tint
app.stage.addChild(sprite);
Quick Creation :
const sprite = Sprite.from('path/to/image.png');
Draw vector shapes programmatically:
import { Graphics } from 'pixi.js';
const graphics = new Graphics();
// Rectangle
graphics.rect(50, 50, 100, 100).fill('blue');
// Circle with stroke
graphics.circle(200, 100, 50).fill('red').stroke({ width: 2, color: 'white' });
// Complex path
graphics
.moveTo(300, 100)
.lineTo(350, 150)
.lineTo(250, 150)
.closePath()
.fill({ color: 0x00ff00, alpha: 0.5 });
app.stage.addChild(graphics);
SVG Support :
graphics.svg('<svg><path d="M 100 350 q 150 -300 300 0" /></svg>');
Optimized container for rendering thousands of sprites:
import { ParticleContainer, Particle, Texture } from 'pixi.js';
const texture = Texture.from('particle.png');
const container = new ParticleContainer({
dynamicProperties: {
position: true, // Allow position updates
scale: false, // Static scale
rotation: false, // Static rotation
color: false // Static color
}
});
// Add 10,000 particles
for (let i = 0; i < 10000; i++) {
const particle = new Particle({
texture,
x: Math.random() * 800,
y: Math.random() * 600
});
container.addParticle(particle);
}
app.stage.addChild(container);
Performance : Up to 10x faster than regular Container for static properties.
Apply per-pixel effects using WebGL shaders:
import { BlurFilter, DisplacementFilter, ColorMatrixFilter } from 'pixi.js';
// Blur
const blurFilter = new BlurFilter({ strength: 8, quality: 4 });
sprite.filters = [blurFilter];
// Multiple filters
sprite.filters = [
new BlurFilter({ strength: 4 }),
new ColorMatrixFilter() // Color transforms
];
// Custom filter area for performance
sprite.filterArea = new Rectangle(0, 0, 200, 100);
Available Filters :
BlurFilter: Gaussian blurColorMatrixFilter: Color transformations (sepia, grayscale, etc.)DisplacementFilter: Warp/distort pixelsAlphaFilter: Flatten alpha across childrenNoiseFilter: Random grain effectFXAAFilter: Anti-aliasingDisplay text with styling:
import { Text, BitmapText, TextStyle } from 'pixi.js';
// Standard Text
const style = new TextStyle({
fontFamily: 'Arial',
fontSize: 36,
fill: '#ffffff',
stroke: { color: '#000000', width: 4 },
filters: [new BlurFilter()] // Bake filter into texture
});
const text = new Text({ text: 'Hello PixiJS!', style });
text.position.set(100, 100);
// BitmapText (faster for dynamic text)
const bitmapText = new BitmapText({
text: 'Score: 0',
style: { fontFamily: 'MyBitmapFont', fontSize: 24 }
});
Performance Tip : Use BitmapText for frequently changing text (scores, counters).
import { Application, Assets, Sprite } from 'pixi.js';
const app = new Application();
await app.init({ width: 800, height: 600 });
document.body.appendChild(app.canvas);
const texture = await Assets.load('bunny.png');
const bunny = new Sprite(texture);
bunny.anchor.set(0.5);
bunny.position.set(400, 300);
bunny.eventMode = 'static'; // Enable interactivity
bunny.cursor = 'pointer';
// Events
bunny.on('pointerdown', () => {
bunny.scale.set(1.2);
});
bunny.on('pointerup', () => {
bunny.scale.set(1.0);
});
bunny.on('pointerover', () => {
bunny.tint = 0xff0000; // Red on hover
});
bunny.on('pointerout', () => {
bunny.tint = 0xffffff; // Reset
});
app.stage.addChild(bunny);
// Animation loop
app.ticker.add((ticker) => {
bunny.rotation += 0.01 * ticker.deltaTime;
});
import { Graphics, Application } from 'pixi.js';
const app = new Application();
await app.init({ width: 800, height: 600 });
document.body.appendChild(app.canvas);
const graphics = new Graphics();
// Rectangle with gradient
graphics.rect(50, 50, 200, 100).fill({
color: 0x3399ff,
alpha: 0.8
});
// Circle with stroke
graphics.circle(400, 300, 80)
.fill('yellow')
.stroke({ width: 4, color: 'orange' });
// Star shape
graphics.star(600, 300, 5, 50, 0).fill({ color: 0xffdf00, alpha: 0.9 });
// Custom path
graphics
.moveTo(100, 400)
.bezierCurveTo(150, 300, 250, 300, 300, 400)
.stroke({ width: 3, color: 'white' });
// Holes
graphics
.rect(450, 400, 150, 100).fill('red')
.beginHole()
.circle(525, 450, 30)
.endHole();
app.stage.addChild(graphics);
// Dynamic drawing (animation)
app.ticker.add(() => {
graphics.clear();
const time = Date.now() * 0.001;
const x = 400 + Math.cos(time) * 100;
const y = 300 + Math.sin(time) * 100;
graphics.circle(x, y, 20).fill('cyan');
});
import { Application, ParticleContainer, Particle, Texture } from 'pixi.js';
const app = new Application();
await app.init({ width: 800, height: 600, backgroundColor: 0x000000 });
document.body.appendChild(app.canvas);
const texture = Texture.from('spark.png');
const particles = new ParticleContainer({
dynamicProperties: {
position: true, // Update positions every frame
scale: true, // Fade out by scaling
rotation: true, // Rotate particles
color: false // Static color
}
});
const particleData = [];
// Create particles
for (let i = 0; i < 5000; i++) {
const particle = new Particle({
texture,
x: 400,
y: 300,
scaleX: 0.5,
scaleY: 0.5
});
particles.addParticle(particle);
particleData.push({
particle,
vx: (Math.random() - 0.5) * 5,
vy: (Math.random() - 0.5) * 5 - 2, // Slight upward bias
life: 1.0
});
}
app.stage.addChild(particles);
// Update loop
app.ticker.add((ticker) => {
particleData.forEach(data => {
// Physics
data.particle.x += data.vx * ticker.deltaTime;
data.particle.y += data.vy * ticker.deltaTime;
data.vy += 0.1 * ticker.deltaTime; // Gravity
// Fade out
data.life -= 0.01 * ticker.deltaTime;
data.particle.scaleX = data.life * 0.5;
data.particle.scaleY = data.life * 0.5;
// Reset particle
if (data.life <= 0) {
data.particle.x = 400;
data.particle.y = 300;
data.vx = (Math.random() - 0.5) * 5;
data.vy = (Math.random() - 0.5) * 5 - 2;
data.life = 1.0;
}
});
});
import { Application, Sprite, Assets, BlurFilter, DisplacementFilter } from 'pixi.js';
const app = new Application();
await app.init({ width: 800, height: 600 });
document.body.appendChild(app.canvas);
const texture = await Assets.load('photo.jpg');
const photo = new Sprite(texture);
photo.position.set(100, 100);
// Blur filter
const blurFilter = new BlurFilter({ strength: 5, quality: 4 });
// Displacement filter (wavy effect)
const displacementTexture = await Assets.load('displacement.jpg');
const displacementSprite = Sprite.from(displacementTexture);
const displacementFilter = new DisplacementFilter({
sprite: displacementSprite,
scale: 50
});
// Apply multiple filters
photo.filters = [blurFilter, displacementFilter];
// Optimize with filterArea
photo.filterArea = new Rectangle(0, 0, photo.width, photo.height);
app.stage.addChild(photo);
// Animate displacement
app.ticker.add((ticker) => {
displacementSprite.x += 1 * ticker.deltaTime;
displacementSprite.y += 0.5 * ticker.deltaTime;
});
import { Filter, GlProgram } from 'pixi.js';
const vertex = `
in vec2 aPosition;
out vec2 vTextureCoord;
uniform vec4 uInputSize;
uniform vec4 uOutputFrame;
uniform vec4 uOutputTexture;
vec4 filterVertexPosition() {
vec2 position = aPosition * uOutputFrame.zw + uOutputFrame.xy;
position.x = position.x * (2.0 / uOutputTexture.x) - 1.0;
position.y = position.y * (2.0*uOutputTexture.z / uOutputTexture.y) - uOutputTexture.z;
return vec4(position, 0.0, 1.0);
}
vec2 filterTextureCoord() {
return aPosition * (uOutputFrame.zw * uInputSize.zw);
}
void main() {
gl_Position = filterVertexPosition();
vTextureCoord = filterTextureCoord();
}
`;
const fragment = `
in vec2 vTextureCoord;
uniform sampler2D uTexture;
uniform float uTime;
void main() {
vec2 uv = vTextureCoord;
// Wave distortion
float wave = sin(uv.y * 10.0 + uTime) * 0.05;
vec4 color = texture(uTexture, vec2(uv.x + wave, uv.y));
gl_FragColor = color;
}
`;
const customFilter = new Filter({
glProgram: new GlProgram({ fragment, vertex }),
resources: {
timeUniforms: {
uTime: { value: 0.0, type: 'f32' }
}
}
});
sprite.filters = [customFilter];
// Update uniform
app.ticker.add((ticker) => {
customFilter.resources.timeUniforms.uniforms.uTime += 0.04 * ticker.deltaTime;
});
import { Application, Assets, AnimatedSprite } from 'pixi.js';
const app = new Application();
await app.init({ width: 800, height: 600 });
document.body.appendChild(app.canvas);
// Load sprite sheet
await Assets.load('spritesheet.json');
// Create animation from frames
const frames = [];
for (let i = 0; i < 10; i++) {
frames.push(Texture.from(`frame_${i}.png`));
}
const animation = new AnimatedSprite(frames);
animation.anchor.set(0.5);
animation.position.set(400, 300);
animation.animationSpeed = 0.16; // ~10 FPS
animation.play();
app.stage.addChild(animation);
// Control playback
animation.stop();
animation.gotoAndPlay(0);
animation.onComplete = () => {
console.log('Animation completed!');
};
class SpritePool {
constructor(texture, initialSize = 100) {
this.texture = texture;
this.available = [];
this.active = [];
// Pre-create sprites
for (let i = 0; i < initialSize; i++) {
this.createSprite();
}
}
createSprite() {
const sprite = new Sprite(this.texture);
sprite.visible = false;
this.available.push(sprite);
return sprite;
}
spawn(x, y) {
let sprite = this.available.pop();
if (!sprite) {
sprite = this.createSprite();
}
sprite.position.set(x, y);
sprite.visible = true;
this.active.push(sprite);
return sprite;
}
despawn(sprite) {
sprite.visible = false;
const index = this.active.indexOf(sprite);
if (index > -1) {
this.active.splice(index, 1);
this.available.push(sprite);
}
}
reset() {
this.active.forEach(sprite => {
sprite.visible = false;
this.available.push(sprite);
});
this.active = [];
}
}
// Usage
const bulletTexture = Texture.from('bullet.png');
const bulletPool = new SpritePool(bulletTexture, 50);
// Spawn bullet
const bullet = bulletPool.spawn(100, 200);
app.stage.addChild(bullet);
// Despawn after 2 seconds
setTimeout(() => {
bulletPool.despawn(bullet);
}, 2000);
import { useEffect, useRef } from 'react';
import { Application } from 'pixi.js';
function PixiCanvas() {
const canvasRef = useRef(null);
const appRef = useRef(null);
useEffect(() => {
const init = async () => {
const app = new Application();
await app.init({
width: 800,
height: 600,
backgroundColor: 0x1099bb
});
canvasRef.current.appendChild(app.canvas);
appRef.current = app;
// Setup scene
// ... add sprites, graphics, etc.
};
init();
return () => {
if (appRef.current) {
appRef.current.destroy(true, { children: true });
}
};
}, []);
return <div ref={canvasRef} />;
}
import * as THREE from 'three';
import { Application, Sprite, Text } from 'pixi.js';
// Three.js scene
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight);
const renderer = new THREE.WebGLRenderer();
document.body.appendChild(renderer.domElement);
// PixiJS overlay
const pixiApp = new Application();
await pixiApp.init({
width: window.innerWidth,
height: window.innerHeight,
backgroundAlpha: 0 // Transparent background
});
pixiApp.canvas.style.position = 'absolute';
pixiApp.canvas.style.top = '0';
pixiApp.canvas.style.left = '0';
pixiApp.canvas.style.pointerEvents = 'none'; // Click through
document.body.appendChild(pixiApp.canvas);
// Add UI elements
const scoreText = new Text({ text: 'Score: 0', style: { fontSize: 24, fill: 'white' } });
scoreText.position.set(20, 20);
pixiApp.stage.addChild(scoreText);
// Render loop
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera); // 3D scene
pixiApp.renderer.render(pixiApp.stage); // 2D overlay
}
animate();
// DON'T: Regular Container (slow for 1000+ sprites)
const container = new Container();
for (let i = 0; i < 10000; i++) {
container.addChild(new Sprite(texture));
}
// DO: ParticleContainer (10x faster)
const particles = new ParticleContainer({
dynamicProperties: { position: true }
});
for (let i = 0; i < 10000; i++) {
particles.addParticle(new Particle({ texture }));
}
// Set filterArea to avoid runtime measurement
sprite.filterArea = new Rectangle(0, 0, 200, 100);
// Release filters when not needed
sprite.filters = null;
// Bake filters into Text at creation
const style = new TextStyle({
filters: [new BlurFilter()] // Applied once at texture creation
});
// Destroy textures when done
texture.destroy();
// Batch destruction with delays to prevent frame drops
textures.forEach((tex, i) => {
setTimeout(() => tex.destroy(), Math.random() * 100);
});
sprite.cullable = true; // Skip rendering if outside viewport
// Use CullerPlugin
import { CullerPlugin } from 'pixi.js';
// Convert complex graphics to texture for faster rendering
const complexShape = new Graphics();
// ... draw many shapes
complexShape.cacheAsBitmap = true; // Renders to texture once
const app = new Application();
await app.init({
antialias: false, // Disable on mobile for performance
resolution: 1, // Lower resolution on low-end devices
autoDensity: true
});
// DON'T: Standard Text (expensive updates)
const text = new Text({ text: `Score: ${score}` });
app.ticker.add(() => {
text.text = `Score: ${++score}`; // Re-renders texture each frame
});
// DO: BitmapText (much faster)
const bitmapText = new BitmapText({ text: `Score: ${score}` });
app.ticker.add(() => {
bitmapText.text = `Score: ${++score}`;
});
Problem : Memory leaks from unreleased GPU resources.
Solution :
// Always destroy sprites and textures
sprite.destroy({ children: true, texture: true, baseTexture: true });
// Destroy filters
sprite.filters = null;
// Destroy graphics
graphics.destroy();
Problem : Changing scale when dynamicProperties.scale = false has no effect.
Solution :
const container = new ParticleContainer({
dynamicProperties: {
position: true,
scale: true, // Enable if you need to update
rotation: true,
color: true
}
});
// If properties are static but you change them, call update:
container.update();
Problem : Filters are expensive; too many cause performance issues.
Solution :
// Limit filter usage
sprite.filters = [blurFilter]; // 1-2 filters max
// Use filterArea to constrain processing
sprite.filterArea = new Rectangle(0, 0, sprite.width, sprite.height);
// Bake filters into textures when possible
const filteredTexture = renderer.filters.generateFilteredTexture({
texture,
filters: [blurFilter]
});
Problem : Updating Text re-generates texture every time.
Solution :
// Use BitmapText for frequently changing text
const bitmapText = new BitmapText({ text: 'Score: 0' });
// Reduce resolution for less memory
text.resolution = 1; // Lower than device pixel ratio
Problem : Calling clear() removes all geometry but doesn't automatically redraw.
Solution :
graphics.clear(); // Remove all shapes
// Redraw new shapes
graphics.rect(0, 0, 100, 100).fill('blue');
Problem : Creating sprites from URLs causes async issues.
Solution :
// DON'T:
const sprite = Sprite.from('image.png'); // May load asynchronously
// DO:
const texture = await Assets.load('image.png');
const sprite = new Sprite(texture);
PixiJS excels at high-performance 2D rendering with WebGL acceleration. Key strengths:
Use for particle systems, 2D games, data visualizations, and interactive canvas applications where performance is critical.
Weekly Installs
137
Repository
GitHub Stars
11
First Seen
Feb 27, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode136
gemini-cli134
codex134
amp134
cline134
github-copilot134
Three.js 3D Web开发教程 - WebGL/WebGPU图形编程、动画与性能优化指南
543 周安装