← Text reveals

Highlighter Reveal

A marker stroke swipes across behind a phrase, as if someone just highlighted the bit that matters. It follows the text across line breaks.

We ship something new every week, not once a year.

How to use it

  1. Paste the snippet just before </head>.
  2. Wrap the phrase in <mark data-highlighter-reveal> (or any inline element).

Options

Add any of these attributes to the same element.

data-colorMarker colour. Default soft purple.
data-heightHow much of the line it covers, e.g. 40% for a low marker line. Default 100%.
data-loopSwipe away and back again.
data-durationTime for the swipe. Default 900 ms.
data-holdHow long it stays, when looping. Default 1800 ms.
1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Highlighter Reveal: a marker stroke swipes across behind a phrase, like
    // someone highlighting the important bit. Wraps across lines naturally.
    // Usage: <p>We ship <mark data-highlighter-reveal>every single week</mark>.</p>
    // Optional: data-color (default soft purple), data-height (how much of the line
    // the stroke covers, e.g. "40%" for a low marker line; default 100%),
    // data-duration (ms), data-loop (swipe away and back again), data-hold (ms).
    var DEFAULTS = { color: 'rgba(168, 85, 247, 0.32)', height: '100%', duration: 900, hold: 1800 };

    var css =
      '[data-highlighter-reveal]{background-color:transparent;color:inherit;background-repeat:no-repeat;' +
      'background-position:0 100%;-webkit-box-decoration-break:clone;box-decoration-break:clone;padding:0 0.12em;border-radius:0.15em}';
    var style = document.createElement('style');
    style.textContent = css;
    document.head.appendChild(style);

    function run(el) {
      var d = el.dataset;
      var h = d.height || DEFAULTS.height;
      var duration = +d.duration || DEFAULTS.duration;
      var loop = d.loop !== undefined && d.loop !== 'false';
      function swipeIn() {
        el.style.backgroundPosition = '0 100%';
        anime.animate(el, {
          backgroundSize: ['0% ' + h, '100% ' + h],
          duration: duration,
          ease: 'inOut(3)',
          onComplete: loop ? function () { setTimeout(swipeOut, +d.hold || DEFAULTS.hold); } : undefined
        });
      }
      function swipeOut() {
        // Leave to the right, the way a pen would lift off.
        el.style.backgroundPosition = '100% 100%';
        anime.animate(el, {
          backgroundSize: ['100% ' + h, '0% ' + h],
          duration: duration * 0.7,
          ease: 'inOut(3)',
          onComplete: function () { setTimeout(swipeIn, 400); }
        });
      }
      swipeIn();
    }

    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);
        });
      }, { threshold: 0.6 });
      document.querySelectorAll('[data-highlighter-reveal]').forEach(function (el) {
        if (el._hr) return;
        el._hr = true;
        var d = el.dataset;
        var color = d.color || DEFAULTS.color;
        el.style.backgroundImage = 'linear-gradient(' + color + ',' + color + ')';
        el.style.backgroundSize = (reduced ? '100% ' : '0% ') + (d.height || DEFAULTS.height);
        if (!reduced) io.observe(el);
      });
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<p>We ship <mark data-highlighter-reveal>every single week</mark>.</p>

<!-- a low yellow marker line: -->
<mark data-highlighter-reveal data-color="#fde047" data-height="40%">important</mark>