← Particles

Confetti Burst

A pop of paper confetti shoots up from an element, tumbles and falls with a little gravity. Perfect for “order placed” or “you did it”.

How to use it

  1. Paste the snippet just before </head>.
  2. Add data-confetti-burst to a button. It bursts on every click.

Options

Add any of these attributes to the same element.

data-trigger="view"Burst once when it scrolls into view instead of on click.
data-countNumber of pieces. Default 70.
data-colorsComma-separated colours.
data-spreadHow far pieces fly, in px. Default 220.

From code, animacious.confetti(element) fires a burst, e.g. after a form is sent.

1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Confetti Burst: a pop of paper confetti shoots up from an element, tumbles
    // and falls with a little gravity. Great for "order placed" or "you did it".
    // Usage: <button data-confetti-burst>Celebrate</button>   (bursts on click)
    // Optional: data-trigger="view" (burst when it scrolls into view instead),
    // data-count (pieces, default 70), data-colors="#a855f7,#facc15,…",
    // data-spread (px, default 220).
    // Script use: animacious.confetti(element) fires a burst from code.
    var DEFAULTS = { count: 70, spread: 220, colors: ['#a855f7', '#6a0dad', '#ec4899', '#facc15', '#38bdf8', '#22c55e'] };

    var css =
      '[data-confetti-burst]{position:relative}' +
      '.cft-piece{position:absolute;left:50%;top:50%;width:8px;height:12px;margin:-6px 0 0 -4px;pointer-events:none;border-radius:1px;z-index:10}';
    var style = document.createElement('style');
    style.textContent = css;
    document.head.appendChild(style);

    function burst(el) {
      if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
      var d = el.dataset;
      var colors = d.colors ? d.colors.split(',').map(function (c) { return c.trim(); }) : DEFAULTS.colors;
      var count = +d.count || DEFAULTS.count;
      var spread = +d.spread || DEFAULTS.spread;
      var R = anime.utils.random;
      for (var i = 0; i < count; i++) {
        var p = document.createElement('span');
        p.className = 'cft-piece';
        p.style.background = colors[i % colors.length];
        if (i % 3 === 0) p.style.borderRadius = '50%';
        el.appendChild(p);
        var duration = R(1400, 2400);
        var rise = -R(spread * 0.4, spread);       // up
        var fall = R(spread * 0.2, spread * 0.8);  // then down past the start
        anime.animate(p, {
          x: { to: R(-spread, spread), duration: duration, ease: 'out(3)' },
          y: [
            { to: rise, duration: duration * 0.35, ease: 'out(2)' },
            { to: fall, duration: duration * 0.65, ease: 'in(2)' }
          ],
          rotate: { to: R(-720, 720), duration: duration, ease: 'linear' },
          rotateX: { to: R(360, 1440), duration: duration, ease: 'linear' },
          opacity: [{ to: 1, duration: duration * 0.7 }, { to: 0, duration: duration * 0.3 }],
          onComplete: (function (piece) { return function () { piece.remove(); }; })(p)
        });
      }
    }

    function init() {
      var io = new IntersectionObserver(function (entries) {
        entries.forEach(function (e) {
          if (!e.isIntersecting) return;
          io.unobserve(e.target);
          burst(e.target);
        });
      }, { threshold: 0.5 });
      document.querySelectorAll('[data-confetti-burst]').forEach(function (el) {
        if (el._cft) return;
        el._cft = true;
        if (el.dataset.trigger === 'view') io.observe(el);
        else el.addEventListener('click', function () { burst(el); });
      });
    }

    window.animacious = window.animacious || {};
    window.animacious.confetti = burst;
    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<button data-confetti-burst>Place order</button>