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:

ChromeFirefoxIEEdgeSafari
8475No8413.1

On mobile and tablet:

Android ChromeAndroid FirefoxAndroidiOS Safari
15115315113.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: fixed with top and left at 0px, so they never affect page layout.
  • pointer-events: none keeps 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.

A button with a gradient exploding into particles