← Buttons

Like Heart Burst

Tap the heart and it pops, fills with colour and throws out a ring and a spray of dots. Tap again to unlike. It’s a real toggle button with aria-pressed.

How to use it

  1. Paste the snippet just before </head>.
  2. Add an empty <button data-like-heart-burst aria-label="Like"></button>. Size it with font-size.

Options

Add any of these attributes to the same element.

data-colorColour when liked. Default #ec4899.
data-likedStart in the liked state.

It fires a like event: button.addEventListener('like', e => save(e.detail.liked)). From code, animacious.like(button) toggles it.

1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Like Heart Burst: tap the heart and it pops, fills with colour and throws
    // out a ring and a spray of little dots. Tap again to unlike.
    // Usage: <button data-like-heart-burst aria-label="Like"></button>
    // Optional: data-color (default #ec4899), data-liked (start liked). Size it
    // with font-size. Listen for the "like" event: e.detail.liked is true/false.
    // Script use: animacious.like(button) toggles it from code.
    var DEFAULTS = { color: '#ec4899' };
    var HEART = 'M12 21s-7.5-4.6-9.6-9.3C.9 8.3 3 4.5 6.7 4.5c2.1 0 3.6 1.1 4.3 2.4.7-1.3 2.2-2.4 4.3-2.4 3.7 0 5.8 3.8 4.3 7.2C19.5 16.4 12 21 12 21z';

    var css =
      '.lhb{position:relative;display:inline-grid;place-items:center;width:2.2em;height:2.2em;padding:0;border:0;background:none;cursor:pointer;color:inherit;-webkit-tap-highlight-color:transparent}' +
      '.lhb svg.lhb-heart{width:1.4em;height:1.4em;overflow:visible}' +
      '.lhb-heart path{fill:transparent;stroke:currentColor;stroke-width:1.8;transition:fill .15s,stroke .15s}' +
      '.lhb.is-liked .lhb-heart path{fill:var(--lhb-color);stroke:var(--lhb-color)}' +
      '.lhb-ring{position:absolute;width:1.6em;height:1.6em;border-radius:50%;border:0.12em solid var(--lhb-color);opacity:0;pointer-events:none}' +
      '.lhb-dot{position:absolute;width:0.22em;height:0.22em;border-radius:50%;background:var(--lhb-color);opacity:0;pointer-events:none}';
    var style = document.createElement('style');
    style.textContent = css;
    document.head.appendChild(style);

    function setup(btn) {
      btn.classList.add('lhb');
      if (btn.tagName === 'BUTTON' && !btn.getAttribute('type')) btn.type = 'button';
      btn.style.setProperty('--lhb-color', btn.dataset.color || DEFAULTS.color);
      if (!btn.getAttribute('aria-label')) btn.setAttribute('aria-label', 'Like');
      btn.innerHTML = '<svg class="lhb-heart" viewBox="0 0 24 24" aria-hidden="true"><path d="' + HEART + '"/></svg>';
      var ring = document.createElement('span');
      ring.className = 'lhb-ring';
      btn.appendChild(ring);
      var dots = [];
      for (var i = 0; i < 8; i++) {
        var dot = document.createElement('span');
        dot.className = 'lhb-dot';
        btn.appendChild(dot);
        dots.push(dot);
      }
      var liked = btn.dataset.liked !== undefined && btn.dataset.liked !== 'false';
      btn.classList.toggle('is-liked', liked);
      btn.setAttribute('aria-pressed', liked);
      btn._lhb = { ring: ring, dots: dots, heart: btn.firstChild };
    }

    function toggle(btn) {
      var s = btn._lhb;
      var liked = !btn.classList.contains('is-liked');
      btn.classList.toggle('is-liked', liked);
      btn.setAttribute('aria-pressed', liked);
      btn.dispatchEvent(new CustomEvent('like', { bubbles: true, detail: { liked: liked } }));
      if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
      if (!liked) {
        anime.animate(s.heart, { scale: [{ to: 0.8, duration: 120 }, { to: 1, duration: 300 }], ease: 'out(2)' });
        return;
      }
      anime.animate(s.heart, {
        scale: [{ to: 0.6, duration: 110, ease: 'in(2)' }, { to: 1, duration: 800, ease: 'outElastic(1, .35)' }]
      });
      anime.animate(s.ring, {
        scale: [0.3, 1.6], opacity: [{ to: 0.9, duration: 80 }, { to: 0, duration: 420 }],
        borderWidth: ['0.3em', '0em'], duration: 500, delay: 80, ease: 'out(3)'
      });
      s.dots.forEach(function (dot, i) {
        var angle = (i / s.dots.length) * Math.PI * 2 + 0.3;
        var dist = 1.25 + (i % 2) * 0.25; // em
        anime.animate(dot, {
          x: ['0em', Math.cos(angle) * dist + 'em'],
          y: ['0em', Math.sin(angle) * dist + 'em'],
          scale: [{ to: 1.2, duration: 200 }, { to: 0, duration: 400 }],
          opacity: [{ to: 1, duration: 50 }, { to: 1, duration: 300 }, { to: 0, duration: 250 }],
          duration: 600, delay: 120, ease: 'out(3)'
        });
      });
    }

    function init() {
      document.querySelectorAll('[data-like-heart-burst]').forEach(function (btn) {
        if (btn._lhb) return;
        setup(btn);
        btn.addEventListener('click', function () { toggle(btn); });
      });
    }

    window.animacious = window.animacious || {};
    window.animacious.like = function (btn) { if (btn._lhb) toggle(btn); };
    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<button data-like-heart-burst aria-label="Like"></button>