Fireworks on click with the Web Animations API
The Web Animations API gives us a single way to describe keyframe animations in JavaScript. This demo uses it to spawn a firework burst of particles from a button on every click.
What browsers currently do
As of writing, every major browser except Safari and Internet Explorer at least partially supports the Web Animations API. Safari support is hidden behind the “Experimental Features” entry in its developer menu.
Support levels differ by version. On desktop:
| Chrome | Firefox | IE | Edge | Safari |
|---|---|---|---|---|
| 84 | 75 | No | 84 | 13.1 |
On mobile and tablet:
| Android Chrome | Android Firefox | Android | iOS Safari |
|---|---|---|---|
| 151 | 153 | 151 | 13.4-13.7 |
For a related effect built on a button, Ana Tudor’s recreation of the Twitter heart animation is worth a look.
Markup and one global style rule
The HTML is minimal: one <button>, though nothing stops us from attaching the effect to another tag, or to clicks anywhere on the page.
<button id="button">Click on me</button>
Every particle shares a few properties, so they belong in the page’s global CSS. Using a custom <particle> tag keeps semantic elements free; animating <p>, <i> or anything else works just as well.
particle {
border-radius: 50%;
left: 0;
pointer-events: none;
position: fixed;
top: 0;
}
- Particles use
position: fixedwithtopandleftat0px, so they never affect page layout.pointer-events: nonekeeps users from interacting with them while they are on screen.
Styling of the button and page is outside the scope here.
The JavaScript side
The script follows five steps: listen for a click on the button; create 30 <particle> elements and append them to the <body>; give each a random width, height and background; animate each from the mouse position to a random destination while it fades out; and remove each element from the DOM once its animation finishes.
Click handling and particle creation
// We first check if the browser supports the Web Animations API
if (document.body.animate) {
// If yes, we add a click listener on our button
document.querySelector('#button').addEventListener('click', pop);
}
// The pop() function is called on every click
function pop(e) {
// Loop to generate 30 particles at once
for (let i = 0; i < 30; i++) {
// We pass the mouse coordinates to the createParticle() function
createParticle(e.clientX, e.clientY);
}
}
function createParticle(x, y) {
// Create a custom particle element
const particle = document.createElement('particle');
// Append the element into the body
document.body.appendChild(particle);
}
Random sizing, then the animation itself
function createParticle (x, y) {
// [...]
// Calculate a random size from 5px to 25px
const size = Math.floor(Math.random() * 20 + 5);
// Apply the size on each particle
particle.style.width = `${size}px`;
particle.style.height = `${size}px`;
// Generate a random color in a blue/purple palette
particle.style.background = `hsl(${Math.random() * 90 + 180}, 70%, 60%)`;
}
function createParticle (x, y) {
// [...]
// Generate a random x & y destination within a distance of 75px from the mouse
const destinationX = x + (Math.random() - 0.5) * 2 * 75;
const destinationY = y + (Math.random() - 0.5) * 2 * 75;
// Store the animation in a variable because we will need it later
const animation = particle.animate([
{
// Set the origin position of the particle
// We offset the particle with half its size to center it around the mouse
transform: `translate(${x - (size / 2)}px, ${y - (size / 2)}px)`,
opacity: 1
},
{
// We define the final coordinates as the second keyframe
transform: `translate(${destinationX}px, ${destinationY}px)`,
opacity: 0
}
], {
// Set a random duration from 500 to 1500ms
duration: 500 + Math.random() * 1000,
easing: 'cubic-bezier(0, .9, .57, 1)',
// Delay every particle with a random value from 0ms to 200ms
delay: Math.random() * 200
});
}
Random delays mean waiting particles sit visible in the top-left corner, so the global CSS sets them to zero opacity.
particle {
/* Same as before */
opacity: 0;
}
Why cleanup matters
Removing particle elements from the DOM is not optional. Each click adds 30 new elements, and the browser’s memory can fill up quickly enough to make the animation janky.
function createParticle (x, y) {
// Same as before
// When the animation is finished, remove the element from the DOM
animation.onfinish = () => {
particle.remove();
};
}
The result, and ways to vary it
With all of the above combined, clicking the button produces a full colorful burst. If no animation appears, the browser likely lacks Web Animations API support — check the support tables above.
Since the particles are styled with CSS, changing their shape or character is straightforward; squares, other shapes and even text characters all work as particle sources.
A different approach is to explode the button element itself, as Zach Saucier demonstrates using canvas.




