← Rotating words

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

  1. Paste the snippet just before </head>.
  2. Wrap the changing word in a <span> and list every word, separated by commas, in data-word-slot-machine.

Options

Add any of these attributes to the same element.

data-holdHow long each word stays. Default 1800 ms.
data-durationTime for each spin. Default 1300 ms.
data-spinsHow many words whizz past each spin. Default 8.

The reel is as wide as the longest word, so the sentence never shifts.

1 · Snippet
<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>
2 · Your HTML
<h1>
  Built for
  <span data-word-slot-machine="makers, teams, dreamers">makers</span>
</h1>