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
- Paste the snippet just before
</head>. - Add an empty
<button data-like-heart-burst aria-label="Like"></button>. Size it withfont-size.
Options
Add any of these attributes to the same element.
data-color | Colour when liked. Default #ec4899. |
data-liked | Start in the liked state. |
It fires a like event: button.addEventListener('like', e => save(e.detail.liked)). From code, animacious.like(button) toggles it.
<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><button data-like-heart-burst aria-label="Like"></button>