← Text reveals

Character Rise

Letters rise one by one from behind each word’s baseline, tipping upright as they arrive. Loop it and they leave upwards before rising again.

Rise into view

How to use it

  1. Paste the snippet just before </head>.
  2. Add data-character-rise to any heading or text element.

Options

Add any of these attributes to the same element.

data-loopRise in, hold, leave upwards and rise again.
data-holdHow long the text stays before leaving, when looping. Default 1600 ms.
data-durationHow long each letter takes to rise. Default 1000 ms.
data-staggerGap between letters. Default 28 ms.
data-delayWait before the first letter. Default 0 ms.
1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Character Rise: letters rise one by one from behind each word's baseline,
    // tipping upright as they arrive.
    // Usage: <h1 data-character-rise>Rise into view</h1>
    // Optional: data-loop (letters leave upwards and rise again), data-hold (ms
    // to stay before leaving), data-duration (ms per letter), data-stagger (ms
    // between letters), data-delay (ms before starting).
    var DEFAULTS = { duration: 1000, stagger: 28, delay: 0, hold: 1600 };

    var css =
      '.crx-word{display:inline-block;white-space:nowrap;clip-path:inset(-0.1em -0.15em -0.2em -0.15em)}' +
      '.crx-char{display:inline-block;transform-origin:0 100%;will-change:transform}';
    var style = document.createElement('style');
    style.textContent = css;
    document.head.appendChild(style);

    function reduced() {
      return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    }

    function split(el) {
      var text = el.textContent.replace(/\s+/g, ' ').trim();
      el.setAttribute('aria-label', text);
      el.textContent = '';
      var chars = [];
      text.split(' ').forEach(function (word, i) {
        if (i) el.appendChild(document.createTextNode(' '));
        var w = document.createElement('span');
        w.className = 'crx-word';
        w.setAttribute('aria-hidden', 'true');
        Array.from(word).forEach(function (c) {
          var s = document.createElement('span');
          s.className = 'crx-char';
          s.textContent = c;
          w.appendChild(s);
          chars.push(s);
        });
        el.appendChild(w);
      });
      return chars;
    }

    function play(el, chars) {
      var d = el.dataset;
      var loop = d.loop !== undefined && d.loop !== 'false';
      var duration = +d.duration || DEFAULTS.duration;
      var stagger = +d.stagger || DEFAULTS.stagger;

      var tl = anime.createTimeline({ loop: loop, delay: +d.delay || DEFAULTS.delay });
      tl.add(chars, {
        y: ['125%', '0%'],
        rotate: [10, 0],
        duration: duration,
        ease: 'outQuart',
        delay: anime.stagger(stagger)
      });
      if (loop) {
        // Leave upwards, so the next rise reads as new text arriving.
        tl.add(chars, {
          y: ['0%', '-125%'],
          rotate: [0, -6],
          duration: duration * 0.6,
          ease: 'inQuart',
          delay: anime.stagger(stagger * 0.6)
        }, '+=' + (+d.hold || DEFAULTS.hold));
      }
    }

    function init() {
      var io = new IntersectionObserver(function (entries) {
        entries.forEach(function (e) {
          if (!e.isIntersecting) return;
          io.unobserve(e.target);
          play(e.target, e.target._crxChars);
        });
      }, { threshold: 0.2 });
      document.querySelectorAll('[data-character-rise]').forEach(function (el) {
        if (el._crxChars) return;
        var chars = split(el);
        el._crxChars = chars;
        if (reduced()) return;
        chars.forEach(function (c) { c.style.transform = 'translateY(125%)'; });
        io.observe(el);
      });
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<h1 data-character-rise>Rise into view</h1>

<!-- or, looping: -->
<h1 data-character-rise data-loop>Rise into view</h1>