← Rotating words

Word Fade Swap

One word in your sentence dissolves into the next: it blurs and lifts away while the new word sharpens into place. Calm, soft and easy to read.

Made for designers

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-fade-swap.

Options

Add any of these attributes to the same element.

data-holdHow long each word stays. Default 2000 ms.
data-durationTime for each swap. Default 700 ms.
data-blurHow blurry words get, in px. Default 10.

The slot 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 Fade Swap: one word in a sentence dissolves into the next, blurring
    // out as it lifts away while the new word sharpens into place.
    // Usage: <h1>Made for <span data-word-fade-swap="designers, developers, everyone">designers</span></h1>
    // Optional: data-hold (ms per word, default 2000), data-duration (ms per
    // swap, default 700), data-blur (px, default 10).
    var DEFAULTS = { hold: 2000, duration: 700, blur: 10 };

    var css =
      '.wfs{display:inline-grid;vertical-align:baseline;text-align:left}' +
      '.wfs-word{grid-area:1/1;white-space:nowrap}' +
      '.wfs-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 build(el) {
      var words = (el.getAttribute('data-word-fade-swap') || el.textContent)
        .split(',').map(function (w) { return w.trim(); }).filter(Boolean);
      el.classList.add('wfs');
      el.textContent = '';
      var sr = document.createElement('span');
      sr.className = 'wfs-sr';
      sr.textContent = words.join(', ');
      el.appendChild(sr);
      // Every word sits in the same grid cell, so the box is as wide as the
      // longest one and the sentence never shifts.
      return words.map(function (w, i) {
        var s = document.createElement('span');
        s.className = 'wfs-word';
        s.textContent = w;
        s.setAttribute('aria-hidden', 'true');
        if (i) s.style.opacity = 0;
        el.appendChild(s);
        return s;
      });
    }

    function run(el, spans) {
      var d = el.dataset;
      var duration = +d.duration || DEFAULTS.duration;
      var blur = (+d.blur || DEFAULTS.blur) + 'px';
      var i = 0;
      setInterval(function () {
        var out = spans[i];
        i = (i + 1) % spans.length;
        var inn = spans[i];
        anime.animate(out, {
          opacity: [1, 0], filter: ['blur(0px)', 'blur(' + blur + ')'], y: ['0em', '-0.25em'],
          duration: duration, ease: 'in(2)'
        });
        anime.animate(inn, {
          opacity: [0, 1], filter: ['blur(' + blur + ')', 'blur(0px)'], y: ['0.25em', '0em'],
          duration: duration, delay: duration * 0.35, ease: 'out(3)'
        });
      }, (+d.hold || DEFAULTS.hold) + duration);
    }

    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);
          run(e.target, e.target._wfs);
        });
      }, { threshold: 0.5 });
      document.querySelectorAll('[data-word-fade-swap]').forEach(function (el) {
        if (el._wfs) return;
        el._wfs = build(el);
        if (!reduced && el._wfs.length > 1) io.observe(el);
      });
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<h1>
  Made for
  <span data-word-fade-swap="designers, developers, everyone">designers</span>
</h1>