Coin Burst
Shiny gold coins burst out of a button, spinning on their edges as they arc up and tumble down. Point them at a wallet or score and they swoop into it instead, which bumps and counts up as each one lands.
120 coins
How to use it
- Paste the snippet just before
</head>. - Add
data-coin-burstto a button. It bursts on every click. - To send the coins somewhere, add
data-targetwith a selector for the wallet or score. Mark the number inside it withdata-coin-countto have it count up.
Options
Add any of these attributes to the same element.
data-target | A selector for an element the coins fly into. It bumps as each one lands. |
data-count | Coins per burst. Default 18. |
data-size | Coin size in px. Default 22. |
data-spread | How far the coins fly, in px. Default 150. |
data-trigger="view" | Burst once when it scrolls into view instead of on click. |
data-loop | Burst by itself every few seconds while in view. Handy for demos. |
The coins are drawn with CSS, so there are no images to load. With reduced motion no coins fly, but a target still counts up. From code, animacious.coinBurst(element) fires a burst, e.g. when a level is won.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Coin Burst: shiny gold coins burst out of an element, spinning as they
// arc up and tumble down, for rewards, wins and "you earned it" moments.
// Give it a target and the coins fly into it instead, like a wallet or a
// score, which bumps as each one lands and can count them up.
// Usage: <button data-coin-burst>Claim reward</button> (bursts on click)
// Optional: data-count (coins per burst, default 18), data-size (coin size
// in px, default 22), data-spread (how far they fly, in px, default 150),
// data-target="#wallet" (a selector: the coins fly into that element; if it
// holds an element with data-coin-count, its number goes up one per coin),
// data-trigger="view" (burst once when it scrolls into view instead of on
// click), data-loop (burst by itself every few seconds while in view).
// Script use: animacious.coinBurst(element) fires a burst from code.
var DEFAULTS = { count: 18, size: 22, spread: 150, every: 2600 };
var css =
'[data-coin-burst]{position:relative}' +
'.cnb-coin{position:absolute;left:50%;top:50%;pointer-events:none;z-index:10;will-change:transform,opacity}' +
'.cnb-face{display:block;width:100%;height:100%;border-radius:50%;box-sizing:border-box;' +
'background:radial-gradient(circle at 34% 28%,#fffbe0 0,#fde68a 18%,#facc15 42%,#eab308 66%,#b45309 100%);' +
'box-shadow:inset 0 0 0 1.5px #a16207,inset 0 0 0 3px #fde047,0 1px 2px rgba(120,53,15,.35);' +
'animation:cnb-spin var(--cnb-spin,600ms) linear infinite}' +
'.cnb-face::after{content:"";position:absolute;inset:26%;border-radius:50%;border:1.5px solid rgba(161,98,7,.55);' +
'box-shadow:inset 1px 1px 0 rgba(255,255,255,.45)}' +
'.cnb-glint{position:absolute;left:50%;top:50%;width:14px;height:14px;margin:-7px 0 0 -7px;pointer-events:none;z-index:11;' +
'background:radial-gradient(circle,#fff 0,rgba(255,255,255,.9) 12%,rgba(253,224,71,0) 60%);' +
'clip-path:polygon(50% 0,60% 40%,100% 50%,60% 60%,50% 100%,40% 60%,0 50%,40% 40%)}' +
// A coin turning on its axis: it narrows to its edge and darkens, then widens again.
'@keyframes cnb-spin{0%{transform:scaleX(1);filter:brightness(1)}25%{transform:scaleX(.08);filter:brightness(.72)}' +
'50%{transform:scaleX(-1);filter:brightness(1.08)}75%{transform:scaleX(-.08);filter:brightness(.72)}100%{transform:scaleX(1);filter:brightness(1)}}';
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
function reduced() { return window.matchMedia('(prefers-reduced-motion: reduce)').matches; }
function bump(target) {
anime.animate(target, { scale: [1.14, 1], duration: 380, ease: 'out(3)' });
var counter = target.matches('[data-coin-count]') ? target : target.querySelector('[data-coin-count]');
if (counter) {
var n = parseInt(counter.textContent.replace(/[^\d-]/g, ''), 10) || 0;
counter.textContent = (n + 1).toLocaleString();
}
}
function burst(el) {
var d = el.dataset;
var target = d.target ? document.querySelector(d.target) : null;
if (reduced()) {
// No flying coins: the target just counts up.
if (target) for (var k = 0; k < (+d.count || DEFAULTS.count); k++) bump(target);
return;
}
var count = +d.count || DEFAULTS.count;
var size = +d.size || DEFAULTS.size;
var spread = +d.spread || DEFAULTS.spread;
var R = anime.utils.random;
// The element pops as the coins leave it.
anime.animate(el, { scale: [{ to: 0.92, duration: 90 }, { to: 1, duration: 420, ease: 'outElastic(1, .5)' }] });
// A couple of glints at the source.
for (var g = 0; g < 3; g++) {
var glint = document.createElement('span');
glint.className = 'cnb-glint';
el.appendChild(glint);
anime.animate(glint, {
x: R(-30, 30), y: R(-24, 10),
scale: [0, R(1.2, 2, 2), 0], rotate: R(0, 90),
duration: 520, delay: g * 90, ease: 'out(2)',
onComplete: (function (n) { return function () { n.remove(); }; })(glint)
});
}
var er = el.getBoundingClientRect();
var tx = 0, ty = 0;
if (target) {
var trc = target.getBoundingClientRect();
tx = trc.left + trc.width / 2 - (er.left + er.width / 2);
ty = trc.top + trc.height / 2 - (er.top + er.height / 2);
}
for (var i = 0; i < count; i++) {
var coin = document.createElement('span');
coin.className = 'cnb-coin';
coin.setAttribute('aria-hidden', 'true');
var s = Math.round(size * R(0.75, 1.15, 2));
coin.style.width = coin.style.height = s + 'px';
coin.style.margin = (-s / 2) + 'px 0 0 ' + (-s / 2) + 'px';
var face = document.createElement('span');
face.className = 'cnb-face';
face.style.setProperty('--cnb-spin', R(420, 900) + 'ms');
face.style.animationDelay = -R(0, 900) + 'ms';
coin.appendChild(face);
el.appendChild(coin);
// Up and out in a fan, mostly upwards.
var a = -Math.PI / 2 + R(-1.15, 1.15, 2);
var reach = spread * R(0.45, 1, 2);
var px = Math.cos(a) * reach, py = Math.sin(a) * reach * 0.9 - spread * 0.15;
var up = R(380, 520);
var delay = i * R(10, 28);
var done = (function (c) { return function () { c.remove(); }; })(coin);
if (target) {
// Arc out, hang for a beat, then swoop into the target.
var fly = R(420, 620);
anime.animate(coin, {
x: [{ to: px, duration: up, ease: 'out(3)' }, { to: tx, duration: fly, delay: R(40, 160), ease: 'inOut(3)' }],
y: [{ to: py, duration: up, ease: 'out(3)' }, { to: ty, duration: fly, delay: 0, ease: 'in(2)' }],
scale: [{ from: 0.3, to: 1, duration: up * 0.6, ease: 'out(3)' }, { to: 0.55, duration: fly, delay: up * 0.4, ease: 'in(2)' }],
delay: delay,
onComplete: (function (c) { return function () { c.remove(); bump(target); }; })(coin)
});
} else {
// Arc out, then fall under gravity with a little sideways drift.
var fallDur = R(700, 1000);
var fall = spread * R(0.9, 1.5, 2);
anime.animate(coin, {
x: { to: px * 1.35, duration: up + fallDur, ease: 'out(2)' },
y: [{ to: py, duration: up, ease: 'out(2)' }, { to: py + fall, duration: fallDur, ease: 'in(2)' }],
scale: { from: 0.3, to: 1, duration: up * 0.6, ease: 'out(3)' },
opacity: [{ to: 1, duration: up + fallDur * 0.6 }, { to: 0, duration: fallDur * 0.4 }],
delay: delay,
onComplete: done
});
}
}
}
function init() {
var once = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (!e.isIntersecting) return;
once.unobserve(e.target);
burst(e.target);
});
}, { threshold: 0.5 });
document.querySelectorAll('[data-coin-burst]').forEach(function (el) {
if (el._cnb) return;
el._cnb = true;
var d = el.dataset;
if (d.trigger === 'view') once.observe(el);
else el.addEventListener('click', function () { burst(el); });
if (d.loop === undefined || reduced()) return;
var timer = null;
new IntersectionObserver(function (entries) {
clearInterval(timer);
if (!entries[0].isIntersecting) return;
setTimeout(function () { burst(el); }, 400);
timer = setInterval(function () { burst(el); }, DEFAULTS.every);
}, { threshold: 0.5 }).observe(el);
});
}
window.animacious = window.animacious || {};
window.animacious.coinBurst = function (el) { if (el) burst(el); };
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><span id="wallet"><span data-coin-count>120</span> coins</span>
<button data-coin-burst data-target="#wallet">Claim reward</button>
<!-- or just a burst: -->
<button data-coin-burst>Spin to win</button>