Content Marketing

How to Make a Confetti Explosion Using JavaScript

Ever feel like your website needs a little more… party? Whether a user just signed up, finished a task, or simply clicked a Celebrate button, a burst of confetti is a classic way to add a touch of delight.

Today, we’re going to build a lightweight, high-performance confetti explosion using the HTML5 Canvas API. No bulky external libraries are required—just a single script and a dream.

See Confetti in Action

Confetti v2.0.0Last Update: May 11, 2026

Was this helpful?

How the Confetti Javascript Works

The script works by dynamically creating a <canvas> element that covers your entire screen. When you click an element with the class .confetti, the script calculates the center of that element and spawns 150 unique particles.

Each particle has its own:

  • Physics: Velocity, gravity, and drag are applied every frame.
  • Aesthetics: Randomized colors from a curated palette and varying sizes.
  • Movement: A spin factor and rotation to make it look like real paper fluttering.
  • Lifecycle: An opacity decay that makes the particles fade out before they are removed from memory.

The JavaScript

You can drop this script into your project.

<script src="confetti.js"></script>

It’s wrapped in an IIFE (Immediately Invoked Function Expression) to keep the global namespace clean.

(function () {
  const PARTICLE_COUNT = 150;
  const COLORS = [
    '#ff0a54', '#ff477e', '#ff7096', '#ff85a1',
    '#fbb1bd', '#f9bec7', '#3a86ff', '#8338ec',
    '#ffbe0b', '#fb5607', '#06d6a0', '#118ab2',
  ];
  const GRAVITY = 0.12;
  const DRAG = 0.98;
  const SPIN_DRAG = 0.97;

  let canvas, ctx, particles, animId;

  function createCanvas() {
    canvas = document.createElement('canvas');
    // Set styles to cover the viewport and ignore mouse events
    canvas.style.cssText =
      'position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:2147483647';
    document.body.appendChild(canvas);
    ctx = canvas.getContext('2d');
    resize();
    window.addEventListener('resize', resize);
  }

  function resize() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
  }

  function randomBetween(a, b) {
    return a + Math.random() * (b - a);
  }

  function createParticle(x, y) {
    const angle = randomBetween(0, 2 * Math.PI);
    const speed = randomBetween(6, 18);
    return {
      x: x,
      y: y,
      vx: Math.cos(angle) * speed,
      vy: Math.sin(angle) * speed - randomBetween(2, 8),
      w: randomBetween(6, 12),
      h: randomBetween(4, 8),
      color: COLORS[Math.floor(Math.random() * COLORS.length)],
      rotation: randomBetween(0, 2 * Math.PI),
      spin: randomBetween(-0.3, 0.3),
      opacity: 1,
      decay: randomBetween(0.006, 0.012),
    };
  }

  function explode(originX, originY) {
    if (!canvas) createCanvas();
    // Restart animation if one is already running
    if (animId) cancelAnimationFrame(animId);

    particles = [];
    for (let i = 0; i < PARTICLE_COUNT; i++) {
      particles.push(createParticle(originX, originY));
    }
    animate();
  }

  function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    for (let i = particles.length - 1; i >= 0; i--) {
      const p = particles[i];

      // Physics logic
      p.vx *= DRAG;
      p.vy *= DRAG;
      p.vy += GRAVITY;
      p.x += p.vx;
      p.y += p.vy;
      p.rotation += p.spin;
      p.spin *= SPIN_DRAG;
      p.opacity -= p.decay;

      if (p.opacity <= 0) {
        particles.splice(i, 1);
        continue;
      }

      // Drawing logic
      ctx.save();
      ctx.translate(p.x, p.y);
      ctx.rotate(p.rotation);
      ctx.globalAlpha = p.opacity;
      ctx.fillStyle = p.color;
      ctx.fillRect(-p.w / 2, -p.h / 2, p.w, p.h);
      ctx.restore();
    }

    if (particles.length > 0) {
      animId = requestAnimationFrame(animate);
    } else {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      animId = null;
    }
  }

  // Global Click Listener
  document.addEventListener('click', function (e) {
    const trigger = e.target.closest('.confetti');
    if (!trigger) return;
    
    const rect = trigger.getBoundingClientRect();
    const cx = rect.left + rect.width / 2;
    const cy = rect.top + rect.height / 2;
    explode(cx, cy);
  });
})();

Implementation Guide

  1. Add the Script: Copy the code above into a .js file and link it at the end of your <body>, or wrap it in <script> tags directly in your HTML.
  2. Tag Your Trigger: You don’t need to write any extra JavaScript for the buttons. Simply add the class confetti to any button, link, or div you want: <button class="confetti">Click Me!</button>
  3. Customize:
    • Want more confetti? Change PARTICLE_COUNT.
    • Want a different vibe? Update the COLORS array with your brand’s hex codes.
    • Want floatier confetti? Decrease the GRAVITY value.

Happy celebrating! 🥳

Related Articles