Fireworks
One button fires 3 staggered DOM rockets that climb and burst into 30–50 colored sparks each — Web Animations API, zero canvas, zero persistent rAF loop.
You'll get access to the interactive demo with a free account.
This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Atmosphere has 57 effects, including 10 free. Explore the category →
3 usage examples



How it works
The effect runs entirely on the Web Animations API (element.animate()) and DOM <div> elements — no <canvas>, no permanent requestAnimationFrame. A click on #fireworksBtn launches three rockets staggered by 200 ms (setTimeout(fn, f * 200)). The X starting position is sampled randomly within the central 80% of the container via getBoundingClientRect(); Y is fixed at 30 px from the bottom.
Each rocket is a <div> (.firework-trail) animated from startY to targetY (between 40 and 120 px from the top) over 400 ms with an ease-out easing. When the animation ends (.onfinish), the trail is removed from the DOM and the explosion fires immediately at the same coordinate.
The explosion creates between 30 and 50 particle <div>s (.firework) positioned at the target. Dispersion angles are evenly distributed over 2π ((2π / particleCount) × i + jitter ≤ 0.5 rad) for a uniform spread. Radial velocity ranges from 50 to 130 px. Motion follows cubic-bezier(0, 0.5, 0.5, 1) — fast exit, soft arrival — simulating gravity deceleration and drag. A +30 px vertical offset at the end position reproduces the gentle fall of sparks. The glow comes from a double-layer colored box-shadow on each particle.
Cleanup is fully automatic: onfinish = () => particle.remove() removes each element as soon as its animation ends (800–1200 ms). There is no object pool and no active loop between clicks — the main thread is free as soon as the last particle disappears.
Accessibility
- prefers-reduced-motion: not handled — no
matchMedia('(prefers-reduced-motion: reduce)')block exists in the code. All 3 explosions fire regardless of the OS setting. Add this check before any accessible deployment: either ignore the click or reduce to 1 rocket and 15 particles. - The
.fireworks-btnbutton is a native<button>— keyboard-focusable, activatable with Enter or Space, no extra JavaScript required. - The
.fireworks-containercarries noroleoraria-label. Dynamically created particle<div>s receive no screen-reader announcement — neutral for a purely decorative effect. - White button text on the amber-to-red gradient (perceived luminance ≈ 0.24) meets the WCAG AA contrast ratio ≥ 4.5:1.
- No informational content is conveyed through the animation alone: application state must be communicated through other means (text, aria-live).
Browser compatibility
Requires the Web Animations API (Element.animate()) — supported in all modern browsers. Zero canvas, zero external dependencies.
If <code>Element.animate</code> is absent (very old browsers), particles are created in the DOM but no animation plays — they appear and disappear in place without movement. No fatal JS error; the page stays functional.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<div class="fireworks-container" id="fireworksContainer">
<button class="fireworks-btn" id="fireworksBtn">Launch!</button>
<!-- .firework-trail and .firework: injected then auto-removed by JS -->
</div>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| fireworkColors (JS array) | 7 rainbow colors | Hex color array for rockets and particles. Replace it before the addEventListener call to enforce your brand palette. |
| Rocket loop: f < 3 | 3 | Number of rockets per click. Change the upper bound in for (let f = 0; f < N; f++): 1 for a single shot, 5 for a festive volley. |
| particleCount: 30 + Math.floor(Math.random() * 20) | 30–50 per rocket | Particle count range per explosion. Reduce to 15 + Math.floor(Math.random() * 10) to lighten the load on slow devices. |
| velocity: 50 + Math.random() * 80 | 50–130 px | Explosion radius in pixels. Increase the base (e.g. 80 + Math.random() * 120) for wider bursts. |
| Trail duration: 400 (ms) | 400 ms | Rocket climb duration. Reduce to 200 ms for an immediate explosion, raise to 700 ms for a more dramatic arc. |
| Particle duration: 800 + Math.random() * 400 | 800–1200 ms | Lifespan of each spark. The random spread avoids simultaneous onfinish callbacks. Reduce to 500 + Math.random() * 200 for a crisper animation. |
| targetY: 40 + Math.random() * 80 | 40–120 px from top | Explosion zone inside the container. Narrow the range (30 + Math.random() * 40) to cluster bursts at the top, or widen it to spread them across the full height. |
FAQ
window. To trigger from a business event (Stripe confirmation, milestone reached), call document.getElementById('fireworksBtn').click() in your callback, or extract the firing logic into a named function before binding it to the button.<div>s created then removed in ≤ 1.2 s. On slow devices, reduce the rocket count (f < 2) or the particle ceiling (20 + Math.floor(Math.random() * 10)) to stay at 60 fps.data-attributes. Colors are defined by the fireworkColors array inside the IIFE. To customize without duplicating the whole script, expose the array as an external variable before the IIFE (let FWK_COLORS = ['#6366f1',…]) and update it from your own code on the fly.