Word Slot Machine
One word in your sentence spins like a slot-machine reel, blurring past other words before it slows down and lands on the next.
Built for makers
How to use it
- Paste the snippet just before
</head>. - Wrap the changing word in a
<span>and list every word, separated by commas, indata-word-slot-machine.
Options
Add any of these attributes to the same element.
data-hold | How long each word stays. Default 1800 ms. |
data-duration | Time for each spin. Default 1300 ms. |
data-spins | How many words whizz past each spin. Default 8. |
The reel is as wide as the longest word, so the sentence never shifts.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Word Slot Machine: one word in a sentence spins like a slot-machine reel,
// blurring past other words before it slows and lands on the next one.
// Usage: <h1>Built for <span data-word-slot-machine="makers, teams, dreamers">makers</span></h1>
// Optional: data-hold (ms per word), data-duration (ms per spin), data-spins
// (words that whizz past each time, default 8).
var DEFAULTS = { hold: 1800, duration: 1300, spins: 8 };
var css =
'.wsm{display:inline-grid;position:relative;vertical-align:baseline;text-align:left;clip-path:inset(0 -0.2em)}' +
'.wsm-size{grid-area:1/1;visibility:hidden;white-space:nowrap}' +
'.wsm-reel{position:absolute;left:0;right:0;top:0;display:flex;flex-direction:column;will-change:transform}' +
'.wsm-reel>span{white-space:nowrap}' +
'.wsm-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}';
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
function setup(el) {
var words = (el.getAttribute('data-word-slot-machine') || el.textContent)
.split(',').map(function (w) { return w.trim(); }).filter(Boolean);
el.classList.add('wsm');
el.textContent = '';
var sr = document.createElement('span');
sr.className = 'wsm-sr';
sr.textContent = words.join(', ');
el.appendChild(sr);
words.forEach(function (w) {
var s = document.createElement('span');
s.className = 'wsm-size';
s.setAttribute('aria-hidden', 'true');
s.textContent = w;
el.appendChild(s);
});
var reel = document.createElement('span');
reel.className = 'wsm-reel';
reel.setAttribute('aria-hidden', 'true');
el.appendChild(reel);
var s = { el: el, words: words, reel: reel, index: 0 };
fill(s, [words[0]]);
return s;
}
function fill(s, list) {
var h = s.el.getBoundingClientRect().height; // exact, not rounded, or the reel drifts
s.reel.textContent = '';
list.forEach(function (w) {
var item = document.createElement('span');
item.textContent = w;
item.style.height = h + 'px';
item.style.lineHeight = h + 'px';
s.reel.appendChild(item);
});
anime.utils.set(s.reel, { y: 0 });
return h;
}
function spin(s, done) {
var d = s.el.dataset;
var n = +d.spins || DEFAULTS.spins;
var next = (s.index + 1) % s.words.length;
// Reel: current word, a blur of random others, then the next word.
var list = [s.words[s.index]];
for (var i = 0; i < n; i++) list.push(s.words[Math.floor(Math.random() * s.words.length)]);
list.push(s.words[next]);
var h = fill(s, list);
var duration = +d.duration || DEFAULTS.duration;
anime.animate(s.reel, {
y: -(list.length - 1) * h,
duration: duration,
ease: 'outBack(1.1)',
onComplete: function () {
s.index = next;
fill(s, [s.words[next]]);
done();
}
});
// Motion blur while it's going fast.
anime.animate(s.reel, {
filter: [{ to: 'blur(1.5px)', duration: duration * 0.2 }, { to: 'blur(0px)', duration: duration * 0.5, delay: duration * 0.1 }],
ease: 'out(2)'
});
}
function init() {
var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (!e.isIntersecting) return;
io.unobserve(e.target);
var s = e.target._wsm;
var hold = +s.el.dataset.hold || DEFAULTS.hold;
(function loop() { setTimeout(function () { spin(s, loop); }, hold); })();
});
}, { threshold: 0.4 });
document.querySelectorAll('[data-word-slot-machine]').forEach(function (el) {
if (el._wsm) return;
var s = (el._wsm = setup(el));
if (!reduced && s.words.length > 1) io.observe(el);
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><h1>
Built for
<span data-word-slot-machine="makers, teams, dreamers">makers</span>
</h1>