Rubber Band
The text stretches wide and squashes flat, then snaps back and forth a few times before it settles, like a pulled elastic band. Play it on scroll, on a loop, or on hover.
Snap back
How to use it
- Paste the snippet just before
</head>. - Add
data-rubber-bandto a heading, button or link.
Options
Add any of these attributes to the same element.
data-trigger | view (default) stretches when scrolled into view. hover stretches when the pointer arrives. |
data-loop | Stretch again every few seconds while on screen (with the view trigger). |
data-interval | Time between stretches, when looping. Default 2400 ms. |
data-strength | How far it stretches. Default 1; try 0.5 for something subtler. |
data-duration | Time for the whole stretch and settle. Default 1000 ms. |
Buttons, links and badges stretch as a whole. On a full-width heading only the text stretches, around its own centre.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Rubber Band: the text stretches wide and squashes flat, then snaps back
// and forth a few times before it settles, like a pulled elastic band.
// Usage: <h1 data-rubber-band>Boing</h1>
// Optional: data-trigger ("view", the default, plays when scrolled into
// view; "hover" plays when the pointer arrives), data-loop (keep stretching
// every few seconds, with the view trigger), data-interval (ms between
// stretches when looping; default 2400), data-strength (how far it
// stretches; default 1), data-duration (ms; default 1000).
var DEFAULTS = { interval: 2400, strength: 1, duration: 1000 };
// [scaleX, scaleY, share of the duration] for each snap: wide and flat,
// tall and thin, and back again, a little less each time.
var SNAPS = [[1.25, 0.75, 0.3], [0.75, 1.25, 0.1], [1.15, 0.85, 0.1], [0.95, 1.05, 0.15], [1.05, 0.95, 0.1], [1, 1, 0.25]];
var css = '.rbb-box{will-change:transform}';
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
// What moves: buttons, badges and other boxes move as a whole. A block
// heading's text goes in an inline box, so it moves around its own centre
// rather than the middle of the full-width block.
function build(el) {
var display = getComputedStyle(el).display;
if (display === 'inline') el.style.display = 'inline-block';
if (display !== 'block') { el.classList.add('rbb-box'); return el; }
var box = document.createElement('span');
box.className = 'rbb-box';
box.style.display = 'inline-block';
while (el.firstChild) box.appendChild(el.firstChild);
el.appendChild(box);
return box;
}
function stretch(el, box) {
var d = el.dataset;
var s = +d.strength || DEFAULTS.strength;
var duration = +d.duration || DEFAULTS.duration;
function keys(axis) {
return SNAPS.map(function (k) {
return { to: 1 + (k[axis] - 1) * s, duration: duration * k[2], ease: 'inOut(2)' };
});
}
return anime.animate(box, { scaleX: keys(0), scaleY: keys(1) });
}
function init() {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
var el = e.target;
el._rbbVisible = e.isIntersecting;
if (e.isIntersecting && !el._rbbPlayed) { el._rbbPlayed = true; el._rbbPlay(); }
});
}, { threshold: 0.5 });
document.querySelectorAll('[data-rubber-band]').forEach(function (el) {
if (el._rbbPlay) return;
var d = el.dataset;
var box = build(el);
var busy = false;
el._rbbPlay = function () {
if (busy) return;
busy = true;
stretch(el, box).then(function () { busy = false; });
};
if (d.trigger === 'hover') {
el.addEventListener('pointerenter', el._rbbPlay);
return;
}
io.observe(el);
if (d.loop !== undefined && d.loop !== 'false') {
// Only while on screen.
setInterval(function () { if (el._rbbVisible) el._rbbPlay(); }, +d.interval || DEFAULTS.interval);
}
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><h1 data-rubber-band>Snap back</h1>
<!-- a button that stretches on hover: -->
<button data-rubber-band data-trigger="hover">Play</button>