Confetti for React Three Fiber, in one instanced mesh

· 2 min read · three.js, react-three-fiber, instancing, open-source

Colored paper confetti, red, green, blue and yellow, falling over the words Confetti V2 on a warm yellow background

Confetti is a good small exercise for instancing. There are hundreds of identical pieces, each with its own position, rotation, size and color, and each lives for a second or two. Drawn as separate meshes, it would be hundreds of draw calls for a celebration that lasts a moment.

The confetti component I published, in two versions, solves it with instanced meshes. This post goes through how the second version is built, because it is short enough to understand in one sitting.

Colored confetti falling over the words Confetti V2

Using it #

<Canvas>
  <ExplosionConfetti />
</Canvas>

Every prop has a default: how many explosions, how many particles in each, their size, how far they spread, how long they last, the colors and where they are. The code is CC0, which means you can do what you like with it.

How it is built #

One instanced mesh per explosion. Every explosion is a drei <Instances> of a small plane. Hundreds of pieces of paper go to the GPU as one draw call, and the explosions are independent, so the number of draw calls is the number of explosions, and not the number of pieces.

Color by attribute. Each particle picks a color from your palette at random, and the colors go into an instancedBufferAttribute on the geometry, read as vertex colors by a basic material. All the colors are in one draw call, with no texture and no extra material.

Short-lived particles. Each piece has its own state: a direction, a rotation speed, a random size and a life counter. Every frame it moves along its direction, rotates and shrinks, and when the life counter passes the duration it is reset at a new random position and starts again. The shrinking is what makes the piece disappear without a fade, and it keeps the effect from needing transparency.

const scaleReduction = 1 - (scaleReductionFactor * life) / 25;
ref.current.scale.set(
  scale.x * Math.max(scaleReduction, 0),
  scale.y * Math.max(scaleReduction, 0),
  scale.z * Math.max(scaleReduction, 0)
);

An explosion counter. explosionCount says how many times each particle is reset before the whole thing removes itself from the scene, so a one-off celebration does not keep running forever.

The limits are on purpose #

The props are clamped in the code: at most 300 particles per explosion and at most 3 explosions. The README also tells you to keep shadows off for better performance. The motion is done in JavaScript, one useFrame per particle, so more particles mean more work on every frame, not just more vertices. Capping them keeps a very large particleAmount from freezing a page.

What I would change #

Looking at it again, two things are worth knowing if you copy it:

  • The duration is counted in frames, not in seconds, so the confetti falls faster on a 144 Hz monitor than on a 60 Hz one. A time-based version would use the frame delta.
  • The motion is on the CPU. For a few hundred particles it is fine. For thousands, the right version stores the particle state in a buffer and animates it in a shader, or with a TSL compute pass on WebGPU.

The repository has the demo and the source, and the project page here has the video.

Related projects