When to Use PixiJS ParticleContainer for Thousands of Sprites
ParticleContainer draws thousands of same-texture sprites in a single batch, but sacrifices per-child styling. Here is how to decide and verify.
07 Sept 2025, 16:45 UTC

The problem: too many draw calls
A regular PixiJS Container treats every Sprite as an individual object. If you add 15,000 sprites for rain, sparks, or stars, the renderer may issue one draw call per sprite and recalculate transforms on the CPU each frame. Frame rate collapses well before you reach the visual target.
The useful takeaway: ParticleContainer batches sprites that share the same BaseTexture into a single draw call and moves transform updates to the GPU. It is built for particle-like effects, not general scene graphs.
What ParticleContainer actually does
A BaseTexture is the underlying GPU texture resource. Multiple Texture objects can reference regions of one BaseTexture, but ParticleContainer requires every child to use the identical base texture. When that condition holds, PixiJS uploads particle data once and draws the whole batch with one WebGL draw call. Position, rotation, scale, and UVs can be updated per particle; everything else is fixed at batch time.
Worked example: 15,000 moving particles
Save the following as a local HTML file and open it in a browser. No build step or special permissions are required. The example uses PixiJS v7.x and generates its own texture so it does not depend on external assets.
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/pixi.js@7/dist/browser/pixi.min.js"></script>
</head>
<body>
<script>
const app = new PIXI.App({
background: 0x101010,
resizeTo: window,
autoStart: true
});
document.body.appendChild(app.view);
// Create a simple shared texture so every particle uses the same BaseTexture.
const canvas = document.createElement('canvas');
canvas.width = 16;
canvas.height = 16;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(8, 8, 8, 0, Math.PI * 2);
ctx.fill();
const texture = PIXI.Texture.from(canvas);
// ParticleContainer: batch size, then which dynamic properties to track.
const particleContainer = new PIXI.ParticleContainer(15000, {
position: true,
rotation: true,
uvs: true,
tint: false
});
const particles = [];
for (let i = 0; i < 15000; i++) {
const sprite = new PIXI.Sprite(texture);
sprite.x = Math.random() * app.screen.width;
sprite.y = Math.random() * app.screen.height;
sprite.vx = (Math.random() - 0.5) * 2;
sprite.vy = (Math.random() - 0.5) * 2;
particleContainer.addChild(sprite);
particles.push(sprite);
}
app.stage.addChild(particleContainer);
app.ticker.add(() => {
for (const sprite of particles) {
sprite.x += sprite.vx;
sprite.y += sprite.vy;
if (sprite.x < 0 || sprite.x > app.screen.width) sprite.vx *= -1;
if (sprite.y < 0 || sprite.y > app.screen.height) sprite.vy *= -1;
}
});
</script>
</body>
</html>
Expected behavior on a mid-range GPU: the batch should remain above 60 fps. If you replace ParticleContainer with a regular PIXI.Container, the same workload typically drops below 20 fps because each sprite becomes a separate draw call. Treat these figures as architecture expectations, not a guaranteed benchmark for your hardware.
Risk: if any particle uses a different texture, the batch breaks. You must group by BaseTexture or accept multiple containers.
Limitations you cannot work around inside the container
- No per-child tint, alpha, or blend mode. Setting
sprite.tinton a particle is ignored. - No filters or masks applied to individual particles.
- No nested containers. Adding a
Containeras a child has no effect. - Every child must reference the same
BaseTexture.
If you need color variation, pre-color the textures or use a custom shader. Do not expect to style particles after they are added.
How to verify the result
- Open the example in Chrome or Firefox and open DevTools > Performance. Record a few seconds of animation.
- Check the draw call count.
ParticleContainershould show one draw call for all 15,000 particles; a regularContainershould show roughly one per sprite. - Add
sprite.tint = 0xff0000;to a few particles after creation. InParticleContainerthe color will not change. In a regularContainerit will. - If you need a fallback, swap
ParticleContainerforPIXI.ContainerorPIXI.SpriteBatchwhen per-sprite styling or hierarchy is required.
When to choose each option
| Use case | Recommended class | Why |
|---|---|---|
| Explosions, rain, star fields, sparks | ParticleContainer |
Many sprites, one texture, transform-only updates. |
| UI buttons, mixed icons, per-item hover states | Container |
Per-child tint, alpha, interaction, and nesting. |
| Different textures, moderate counts | SpriteBatch or multiple ParticleContainer instances |
Batching without the strict single-texture rule. |
Bottom line
Use ParticleContainer when you have thousands of similar sprites that share a texture and only need position, rotation, scale, or UV changes. Verify with a draw-call check and a tint test before committing to it in production. If your effect requires per-particle color, filters, or nested grouping, a regular Container is the safer default.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.