Confetti marks a moment worth celebrating, such as a finished order or a completed goal.
Give a button the confetti class, and each click bursts
confetti from its center over the whole page. The pieces take the
theme's primary, secondary and tertiary colors and their containers.
The component draws them on a canvas that lets clicks through and
removes the canvas when the last piece fades. AutoInit() starts every
.confetti.
<button type="button" class="filled confetti">Celebrate</button>
On a canvas
A <canvas class="confetti" aria-hidden="true">
covers its nearest positioned ancestor and bursts inside it once
when it starts. To burst again, call fire() on its
instance. To wait for the first call instead, set
manualStart.
A small canvas needs a lower startVelocity to keep the
pieces in view.
<div style="position: relative; height: 320px">
<canvas class="confetti no-autoinit" aria-hidden="true"></canvas>
</div>
<script>
const canvas = document.querySelector('canvas.confetti');
const confetti = Expressive.Confetti.init(canvas, { startVelocity: 30, origin: { y: 0.6 } });
button.addEventListener('click', () => confetti.fire());
</script>
From script
Expressive.Confetti.fire(options) bursts over the page
with no markup, for example when a request succeeds. It returns a
promise that resolves when the last piece is gone.
await saveOrder();
Expressive.Confetti.fire({ particleCount: 100, spread: 70, origin: { y: 0.6 } });
Examples
Combine several calls for bigger effects. origin is a
fraction of the viewport, so { x: 0 } is the left
edge.
Random direction
Expressive.Confetti.fire({
angle: 55 + Math.random() * 70,
spread: 50 + Math.random() * 20,
particleCount: 50 + Math.floor(Math.random() * 50),
origin: { y: 0.6 }
});
Fireworks
const end = Date.now() + 5000;
const timer = setInterval(() => {
const left = end - Date.now();
if (left <= 0) return clearInterval(timer);
const burst = { spread: 360, startVelocity: 30, ticks: 60, particleCount: 50 * left / 5000 };
Expressive.Confetti.fire({ ...burst, origin: { x: 0.1 + Math.random() * 0.2, y: Math.random() - 0.2 } });
Expressive.Confetti.fire({ ...burst, origin: { x: 0.7 + Math.random() * 0.2, y: Math.random() - 0.2 } });
}, 250);
Side cannons
const end = Date.now() + 3000;
const colors = ['#a786ff', '#fd8bbc', '#eca184', '#f8deb1'];
(function frame() {
if (Date.now() > end) return;
Expressive.Confetti.fire({ particleCount: 2, angle: 60, spread: 55, startVelocity: 60, origin: { x: 0, y: 0.5 }, colors });
Expressive.Confetti.fire({ particleCount: 2, angle: 120, spread: 55, startVelocity: 60, origin: { x: 1, y: 0.5 }, colors });
requestAnimationFrame(frame);
})();
Stars
const stars = {
spread: 360, ticks: 50, gravity: 0, decay: 0.94, startVelocity: 30,
shapes: ['star'], colors: ['#ffe400', '#ffbd00', '#e89400', '#ffca6c', '#fdffb8']
};
for (const delay of [0, 100, 200]) {
setTimeout(() => {
Expressive.Confetti.fire({ ...stars, particleCount: 40 });
Expressive.Confetti.fire({ ...stars, particleCount: 10, shapes: ['circle'], scalar: 0.75 });
}, delay);
}
Accessibility
- Confetti is decoration. Say what happened in text as well, for example in a snackbar, and keep bursts for rare moments.
- Mark an authored canvas
aria-hidden="true". The page canvas hides itself. - The component draws nothing when the reader prefers reduced motion, and
fire()resolves at once. - The page canvas is a manual popover with pointer events off, so it draws above an open modal dialog without blocking clicks or moving focus.
Options
| Name | Type | Default | Description |
|---|---|---|---|
particleCount | Number | 50 | Pieces per burst. |
angle | Number | 90 | Launch direction in degrees; 90 is straight up and 0 is right. |
spread | Number | 45 | Degrees the pieces fan out from the angle. |
startVelocity | Number | 45 | Launch speed in pixels per frame. |
decay | Number | 0.9 | Share of its speed a piece keeps each frame. |
gravity | Number | 1 | Pull toward the bottom; 0 floats. |
drift | Number | 0 | Sideways push per frame; negative drifts left. |
ticks | Number | 200 | Frames a piece lives while it fades, counted at 60 per second. |
origin | Object | { x: 0.5, y: 0.5 } | Launch point as fractions of the canvas from 0 to 1; a missing side is 0.5. A button launches from its own center instead. |
colors | Array | [] | CSS colors. Empty uses the theme colors. |
shapes | Array | ['square', 'circle'] | Any of 'square', 'circle' and 'star'. |
scalar | Number | 1 | Piece size multiplier. |
manualStart | Boolean | false | On a canvas, wait for fire() instead of bursting at init. |
Methods
Expressive.Confetti.fire(options); // bursts over the page; resolves when it ends
const confetti = Expressive.Confetti.getInstance(element);
confetti.fire({ spread: 90 }); // this canvas, or from this button's center
confetti.destroy(); // removes the click handler, or clears the canvas