Click Ripple
A circle of light spreads out from exactly where a button was pressed, then fades, so every tap gets clear, physical feedback.
How to use it
- Paste the snippet just before
</head>. - Add
data-click-rippleto buttons or any clickable element.
Options
Add any of these attributes to the same element.
data-ripple-color | Ripple colour. Default white at 35%. Use currentColor on light buttons. |
data-duration | How long a ripple lasts. Default 700 ms. |
From code, animacious.ripple(element) fires one from the centre.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Click Ripple: a circle of light spreads out from exactly where a button was
// pressed, then fades, so every tap gets visible feedback.
// Usage: <button data-click-ripple>Save</button>
// Optional: data-ripple-color (default: white at 35%, or the text colour on
// light buttons via data-ripple-color="currentColor"), data-duration (ms).
// Script use: animacious.ripple(element, x, y) fires one from page coordinates.
var DEFAULTS = { duration: 700, color: 'rgba(255,255,255,.35)' };
var css =
'[data-click-ripple]{position:relative;overflow:hidden;isolation:isolate}' +
'.rp-dot{position:absolute;border-radius:50%;pointer-events:none;z-index:-1;transform:scale(0)}';
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
function ripple(el, clientX, clientY) {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
var r = el.getBoundingClientRect();
var x = clientX === undefined ? r.width / 2 : clientX - r.left;
var y = clientY === undefined ? r.height / 2 : clientY - r.top;
// Big enough to reach the farthest corner from the press point.
var size = 2 * Math.hypot(Math.max(x, r.width - x), Math.max(y, r.height - y));
var dot = document.createElement('span');
dot.className = 'rp-dot';
var color = el.dataset.rippleColor || DEFAULTS.color;
dot.style.cssText += 'left:' + (x - size / 2) + 'px;top:' + (y - size / 2) + 'px;width:' + size + 'px;height:' + size + 'px;' +
'background:' + color + (color === 'currentColor' ? ';opacity:.25' : '');
el.appendChild(dot);
var duration = +el.dataset.duration || DEFAULTS.duration;
anime.animate(dot, { scale: [0, 1], duration: duration, ease: 'out(3)' });
anime.animate(dot, {
opacity: 0,
duration: duration * 0.6,
delay: duration * 0.4,
ease: 'linear',
onComplete: function () { dot.remove(); }
});
}
document.addEventListener('pointerdown', function (e) {
var el = e.target.closest && e.target.closest('[data-click-ripple]');
if (el) ripple(el, e.clientX, e.clientY);
});
// Keyboard presses ripple from the centre.
document.addEventListener('keydown', function (e) {
if (e.key !== 'Enter' && e.key !== ' ') return;
var el = e.target.closest && e.target.closest('[data-click-ripple]');
if (el) ripple(el);
});
window.animacious = window.animacious || {};
window.animacious.ripple = ripple;
})();
</script><button data-click-ripple>Save changes</button>