← Text effects

Shine Sweep

A bright, slanted glint passes across the letters every few seconds, like light catching polished metal. The text keeps its own colour; only the letters catch the light.

Gold standard

How to use it

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

Options

Add any of these attributes to the same element.

data-triggerview (default) shines every few seconds while on screen. hover shines when the pointer arrives.
data-colorThe glint’s colour. Default white.
data-intervalTime between glints. Default 2800 ms.
data-durationTime for one pass. Default 1100 ms.

Shows up best on coloured or dark text. On white text, use a coloured glint.

1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Shine Sweep: a bright glint passes across the letters every few seconds,
    // like light catching polished metal. The text keeps its own colour.
    // Usage: <h1 data-shine-sweep>Premium</h1>
    // Optional: data-trigger ("view", the default, shines every few seconds
    // while on screen; "hover" shines when the pointer arrives), data-color
    // (the glint; default white), data-interval (ms between glints; default
    // 2800), data-duration (ms for one pass; default 1100).
    var DEFAULTS = { color: 'rgba(255, 255, 255, 0.95)', interval: 2800, duration: 1100 };
    // Where the glint layer sits before and after a pass: just off the left
    // edge, then just off the right. (The layer is twice as wide as the text,
    // and background percentages are odd: these move it by about 1.4 widths.)
    var FROM = '120% 0%';
    var TO = '-20% 0%';

    var css =
      '.shn{background-repeat:no-repeat;background-size:200% 100%;background-position:' + FROM + ';' +
      '-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;' +
      '-webkit-box-decoration-break:clone;box-decoration-break:clone}';
    var style = document.createElement('style');
    style.textContent = css;
    document.head.appendChild(style);

    // The text's own colour fills the letters (as a background colour, clipped
    // to the text), with a narrow, slanted glint on top.
    function paint(el) {
      var glint = el.dataset.color || DEFAULTS.color;
      el.style.backgroundColor = getComputedStyle(el).color;
      el.style.backgroundImage = 'linear-gradient(110deg, transparent 40%, ' + glint + ' 50%, transparent 60%)';
      el.classList.add('shn');
    }

    function shine(el) {
      return anime.animate(el, {
        backgroundPosition: [FROM, TO],
        duration: +el.dataset.duration || DEFAULTS.duration,
        ease: 'inOutSine'
      });
    }

    function init() {
      if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
      document.querySelectorAll('[data-shine-sweep]').forEach(function (el) {
        if (el._shn) return;
        el._shn = true;
        paint(el);
        var d = el.dataset;
        var busy = false;
        function once() {
          if (busy) return;
          busy = true;
          shine(el).then(function () { busy = false; });
        }
        if (d.trigger === 'hover') {
          el.addEventListener('pointerenter', once);
          return;
        }
        // Only shine while visible, starting soon after it scrolls in.
        var timer = null;
        new IntersectionObserver(function (entries) {
          clearInterval(timer);
          if (!entries[0].isIntersecting) return;
          setTimeout(once, 400);
          timer = setInterval(once, +d.interval || DEFAULTS.interval);
        }, { threshold: 0.4 }).observe(el);
      });
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<h1 data-shine-sweep>Gold standard</h1>

<!-- shine on hover, e.g. a pricing tier: -->
<h3 data-shine-sweep data-trigger="hover">Pro</h3>