关于 pixijs-2d
pixijs-2d 是面向使用 PixiJS 构建交互式 2D 图形和 Canvas 应用的开发者技能指南,涵盖精灵、粒子、滤镜、绘图及 WebGL/WebGPU 渲染优化。
主要功能
- 01精灵与纹理管理
- 02程序化矢量绘图
- 03使用 ParticleContainer 渲染大量精灵
- 04基于着色器的视觉滤镜
- 05精灵表动画
适用场景
- 01制作 2D 游戏和交互式 Canvas 应用
- 02构建含大量精灵的粒子效果
- 03为 3D 场景添加 2D UI 覆盖层
- 04渲染数据可视化
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ freshtechbro/ claudedesignskills --skill pixijs-2d安装后在 Claude Code 中输入/pixijs-2d确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/examples/README.mdassets/starter_pixijs/index.htmlmain.jsREADME.mdstyles.cssreferences/api_reference.mdfilters_effects.mdperformance_guide.mdscripts/particle_builder.pysprite_generator.py详细介绍
PixiJS 2D 渲染技能
快速、轻量的 2D 渲染引擎,可使用 WebGL/WebGPU 创建交互式图形、粒子效果和基于画布的应用。
何时使用此技能
遇到以下情况时触发此技能:
- “创建 2D 粒子效果”或“动画粒子”
- “2D 精灵动画”或“精灵图集处理”
- “交互式画布图形”或“2D 游戏”
- “在 3D 场景上叠加 UI”或“HUD 图层”
- “以编程方式绘制形状”或“矢量图形 API”
- “优化渲染性能”或“数千个精灵”
- “应用视觉滤镜”或“模糊/位移效果”
- “轻量级 2D 引擎”或“Canvas2D 的替代方案”
使用 PixiJS 的场景:高性能 2D 渲染(最多支持 100,000+ 个精灵)、粒子系统、交互式 UI、2D 游戏、使用 WebGL 加速的数据可视化。
不适用的场景:3D 图形(使用 Three.js/R3F)、简单动画(使用 Motion/GSAP)、基本 DOM 操作。
核心概念
1. 应用与渲染器
PixiJS 应用的入口:
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);
关键属性:
app.stage:所有显示对象的根容器app.renderer:WebGL/WebGPU 渲染器实例app.ticker:动画更新循环app.screen:画布尺寸
2. 精灵与纹理
从图像加载的核心视觉元素:
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);
快速创建:
const sprite = Sprite.from('path/to/image.png');
3. Graphics API
以编程方式绘制矢量形状:
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 支持:
graphics.svg('<svg><path d="M 100 350 q 150 -300 300 0" /></svg>');
4. ParticleContainer
用于渲染数千个精灵的优化容器:
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);
性能:对于静态属性,速度最高可比常规 Container 快 10 倍。
5. 滤镜
使用 WebGL 着色器应用逐像素效果:
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);
可用滤镜:
BlurFilter:高斯模糊ColorMatrixFilter:颜色变换(棕褐色、灰度等)DisplacementFilter:扭曲/变形像素AlphaFilter:统一子项的 Alpha 值NoiseFilter:随机颗粒效果FXAAFilter:抗锯齿
6. 文本渲染
显示带样式的文本:
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 }
});
性能提示:对于频繁变化的文本(分数、计数器),请使用 BitmapText。
常见模式
模式 1:基础交互式精灵
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;
});
模式 2:使用 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');
});
模式 3:使用 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;
}
});
});
模式 4:应用滤镜
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;
});
模式 5:使用着色器创建自定义滤镜
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;
});
模式 6:精灵图集动画
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!');
};
模式 7:使用对象池提升性能
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);
集成模式
React 集成
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 覆层(3D 上的 2D UI)
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();
性能最佳实践
1. 大量精灵时使用 ParticleContainer
// 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. 优化滤镜用法
// 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. 管理纹理内存
// 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. 为屏幕外对象启用剔除
sprite.cullable = true; // Skip rendering if outside viewport
// Use CullerPlugin
import { CullerPlugin } from 'pixi.js';
5. 将静态图形缓存为位图
// Convert complex graphics to texture for faster rendering
const complexShape = new Graphics();
// ... draw many shapes
complexShape.cacheAsBitmap = true; // Renders to texture once
6. 优化渲染器设置
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. 动态文本使用 BitmapText
// 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}`;
});
常见陷阱
陷阱 1:不销毁对象
问题:未释放的 GPU 资源会造成内存泄漏。
解决方案:
// Always destroy sprites and textures
sprite.destroy({ children: true, texture: true, baseTexture: true });
// Destroy filters
sprite.filters = null;
// Destroy graphics
graphics.destroy();
陷阱 2:更新静态 ParticleContainer 属性
问题:当 dynamicProperties.scale = false 时,更改 scale 不会生效。
解决方案:
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();
陷阱 3:过度使用滤镜
问题:滤镜开销很大;使用过多会导致性能问题。
解决方案:
// 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]
});
陷阱 4:频繁更新文本
问题:每次更新 Text 都会重新生成纹理。
解决方案:
// 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
陷阱 5:调用 Graphics clear() 后不重绘
问题:调用 clear() 会移除所有几何图形,但不会自动重绘。
解决方案:
graphics.clear(); // Remove all shapes
// Redraw new shapes
graphics.rect(0, 0, 100, 100).fill('blue');
陷阱 6:未使用资源加载
问题:从 URL 创建精灵会导致异步问题。
解决方案:
// DON'T:
const sprite = Sprite.from('image.png'); // May load asynchronously
// DO:
const texture = await Assets.load('image.png');
const sprite = new Sprite(texture);
资源
- 官方网站:https://pixijs.com
- API 文档:https://pixijs.download/release/docs/
- 示例:https://pixijs.io/examples/
- GitHub:https://github.com/pixijs/pixijs
- 滤镜库:@pixi/filter-* packages
- 社区:https://github.com/pixijs/pixijs/discussions
相关技能
- threejs-webgl:用于 3D 图形;PixiJS 可提供 2D UI 覆盖层
- gsap-scrolltrigger:用于通过滚动动画化 PixiJS 属性
- motion-framer:用于在 PixiJS 画布旁为 React 组件添加动画
- react-three-fiber:类似的 React 集成模式
总结
PixiJS 擅长借助 WebGL 加速实现高性能 2D 渲染。主要优势:
- 性能:以 60 FPS 渲染 100,000+ 个精灵
- ParticleContainer:静态属性的处理速度提升 10 倍
- 滤镜:由 WebGL 驱动的视觉效果
- 图形 API:直观的矢量绘制
- 资源管理:可靠的纹理和精灵图集处理
适用于粒子系统、2D 游戏、数据可视化和交互式画布应用,尤其是对性能要求较高的场景。