10k Sprites and a Single Draw Call: Using PixiJS ParticleContainer Correctly
ParticleContainer trades features for batching in PixiJS. Learn what transforms are supported, how to share a texture for a single draw call, and where the container breaks down.
08 Aug 2026, 08:19 UTC

Your scene runs fine with 200 sprites and falls apart at 2,000. The GPU can handle the pixels, but the CPU is choking on scene graph traversal and draw call overhead. The practical fix in PixiJS is not more hardware, it is fewer batches. ParticleContainer is the container that trades features for batching.
The bottleneck is batches, not sprites
A standard PIXI.Container renders each child with its own set of state changes. Masks, filters, blend modes and per-object transforms force the renderer to break the batch and issue a new draw call. A draw call is the CPU work of telling the GPU what to render.
ParticleContainer is a specialized container optimized for large numbers of sprites. It reduces CPU overhead during traversal and render by keeping the render state static and packing vertices into one batch. The gain comes from constraints.
What ParticleContainer allows and restricts
Supported per-sprite properties are limited to position, scale, rotation and alpha. Those map directly to a simple vertex shader. Complex features are disabled by design.
- No filters or masks on children.
- No nested Containers inside the ParticleContainer.
- Non-Sprite children cause runtime errors. Only PIXI.Sprite instances are accepted.
Batching is most effective when all children share the same base texture. Sharing a texture, typically from an atlas, lets PixiJS reuse the same GPU texture binding across the batch. Mixing many unique textures breaks the batch into multiple draw calls.
Configuration is done at construction. The first argument is maxSize, the maximum number of children. The second argument is a properties object that toggles which transforms are updated.
const particleContainer = new PIXI.ParticleContainer(10000, {
scale: true,
rotation: true,
alpha: true,
position: true
});
Worked example: confetti from a shared atlas
Use one texture for all pieces and vary position, scale and rotation per frame.
// create once
const app = new PIXI.Application({ antialias: false });
const particleContainer = new PIXI.ParticleContainer(5000, { scale:true, rotation:true, alpha:true });
app.stage.addChild(particleContainer);
const texture = PIXI.Texture.from('confetti-atlas.png');
for (let i = 0; i < 2000; i++) {
const sprite = new PIXI.Sprite(texture);
sprite.anchor.set(0.5);
sprite.x = Math.random() * app.screen.width;
sprite.y = Math.random() * app.screen.height;
sprite.scale.set(0.5 + Math.random());
sprite.rotation = Math.random() * Math.PI * 2;
sprite.alpha = 0.7 + Math.random() * 0.3;
particleContainer.addChild(sprite);
}
All sprites reference the same texture. The properties object enables the transforms you actually animate. If you never animate rotation, set rotation:false to reduce work.
A practical way to check the result is to compare draw call counts between a standard Container and a ParticleContainer with the same sprites. In development you can log renderer state after the first render and inspect how many batches were issued. Fewer batches with the ParticleContainer indicates batching is working.
Trade-offs and where it breaks
ParticleContainer is not a general purpose container. Tinting and advanced blend modes are version dependent and may be unsupported. Hierarchical animation is not possible because nesting is disallowed. If you need per-sprite filters, masks, or child containers, you must use a regular Container and accept more draw calls.
Adding a Graphics object or a Container to a ParticleContainer will throw a type error at runtime. The restriction is intentional; the renderer expects a flat list of sprites with a known vertex layout.
Limitations are also visible in asset planning. Using many different textures defeats the purpose. An atlas with sprite frames from the same base texture preserves batching while giving visual variety.
Use ParticleContainer when you have thousands of visually similar sprites that only need position, scale, rotation and alpha. Use a standard Container when you need filters, masks, nesting, or per-sprite material changes. That decision is the performance lever.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.