← Text effects

Liquid Chrome Text

The letters turn to polished liquid metal: a mirrored horizon rolls up and down inside them, bright streaks slide across, and a gentle ripple keeps the surface moving like mercury. Comes in chrome, gold and rose.

Liquid metal

How to use it

  1. Paste the snippet just before </head>.
  2. Add data-liquid-chrome-text to a big, bold heading. Dark backgrounds make the shine stand out.

Options

Add any of these attributes to the same element.

data-metalchrome (default), gold or rose.
data-speedHow fast the reflections flow. Default 1.
data-rippleHow much the surface wobbles. Default 0.05 em; 0 keeps the edges still.
1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Liquid Chrome Text: the letters look like polished liquid metal. A
    // mirrored horizon and bright streaks flow over them, and a gentle ripple
    // keeps the surface (and the edges) moving like mercury.
    // Usage: <h1 data-liquid-chrome-text>Chrome</h1>
    // Optional: data-metal (chrome (default), gold or rose), data-speed (how
    // fast the reflections flow; default 1), data-ripple (how much the surface
    // wobbles, in em; default 0.05, 0 for none).
    var DEFAULTS = { metal: 'chrome', speed: 1, ripple: 0.05 };
    var count = 0;

    var METALS = {
      chrome: ['#f4f8ff', '#a9b8cc', '#ffffff', '#1f2430', '#596273', '#c9d2de', '#ffffff', '#7d8799'],
      gold: ['#fff8dc', '#e3b54f', '#fffbe8', '#5a3b0c', '#a87822', '#f2cf7a', '#fff6d5', '#b38631'],
      rose: ['#fff1f2', '#e8a3ad', '#ffffff', '#5b2430', '#a6576a', '#f3c2cb', '#fff5f6', '#b97483']
    };

    var css =
      '.lqc-text{display:inline-block;-webkit-background-clip:text;background-clip:text;color:transparent;padding:0 .06em;-webkit-box-decoration-break:clone;box-decoration-break:clone}' +
      '.lqc-svg{position:absolute;width:0;height:0;overflow:hidden}';
    var style = document.createElement('style');
    style.textContent = css;
    document.head.appendChild(style);

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

    function setup(el) {
      var d = el.dataset;
      var m = METALS[d.metal] || METALS[DEFAULTS.metal];
      var speed = d.speed !== undefined ? +d.speed : DEFAULTS.speed;
      var ripple = d.ripple !== undefined ? +d.ripple : DEFAULTS.ripple;

      var text = el.textContent.replace(/\s+/g, ' ').trim();
      el.setAttribute('aria-label', text);
      el.textContent = '';
      var span = document.createElement('span');
      span.className = 'lqc-text';
      span.setAttribute('aria-hidden', 'true');
      span.textContent = text;
      el.appendChild(span);

      // Layers, top first: two soft streaks of light, then the mirrored
      // horizon (sky, bright line, dark ground) that gives metal its look.
      var streaks =
        'linear-gradient(105deg, transparent 38%, rgba(255,255,255,.85) 47%, rgba(255,255,255,0) 56%),' +
        'linear-gradient(75deg, transparent 42%, rgba(255,255,255,.5) 50%, transparent 58%),';
      function horizon(angle) {
        return 'linear-gradient(' + angle.toFixed(1) + 'deg, ' + m[0] + ' 0%, ' + m[1] + ' 24%, ' + m[2] + ' 44%, ' + m[3] + ' 49%, ' + m[4] + ' 58%, ' + m[5] + ' 72%, ' + m[6] + ' 86%, ' + m[7] + ' 100%)';
      }
      span.style.backgroundSize = '260% 100%, 300% 100%, 100% 170%';
      span.style.backgroundRepeat = 'no-repeat, no-repeat, repeat';

      var fs = parseFloat(getComputedStyle(el).fontSize);
      var turb = null, disp = null, id = null;
      if (ripple > 0) {
        id = 'lqc-ripple-' + (++count);
        var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        svg.setAttribute('class', 'lqc-svg');
        svg.setAttribute('aria-hidden', 'true');
        svg.innerHTML =
          '<filter id="' + id + '" x="-5%" y="-10%" width="110%" height="120%">' +
          '<feTurbulence type="fractalNoise" numOctaves="2" seed="' + Math.floor(Math.random() * 100) + '" result="n"/>' +
          '<feDisplacementMap in="SourceGraphic" in2="n" xChannelSelector="R" yChannelSelector="G"/>' +
          '</filter>';
        el.appendChild(svg);
        turb = svg.querySelector('feTurbulence');
        disp = svg.querySelector('feDisplacementMap');
      }
      var shadow = ' drop-shadow(0 .05em .03em rgba(0,0,0,.28))';

      function size() {
        fs = parseFloat(getComputedStyle(el).fontSize) || fs;
        if (disp) disp.setAttribute('scale', (fs * ripple).toFixed(2));
        span.style.filter = (id ? 'url(#' + id + ')' : '') + shadow;
      }
      size();
      new ResizeObserver(size).observe(el);

      function frame(t) {
        var s = t * 0.001 * speed;
        // The horizon rolls up and down, the streaks sweep across at their own pace.
        var hy = 50 + Math.sin(s * 0.9) * 30 + Math.sin(s * 2.3) * 8;
        // The horizon tilts back and forth, like a reflection on a moving surface.
        span.style.backgroundImage = streaks + horizon(180 + Math.sin(s * 0.6) * 9 + Math.sin(s * 1.7) * 3);
        var a = ((s * 0.22) % 1.6) - 0.3;
        var b = 1.3 - ((s * 0.15 + 0.5) % 1.6);
        span.style.backgroundPosition = (a * 100).toFixed(1) + '% 0, ' + (b * 100).toFixed(1) + '% 0, 0 ' + hy.toFixed(1) + '%';
        if (turb) {
          // A slowly changing noise makes the surface swell and settle.
          var fx = (0.9 + 0.25 * Math.sin(s * 0.7)) / (fs * 1.6);
          var fy = (1.6 + 0.4 * Math.cos(s * 0.55)) / (fs * 1.6);
          turb.setAttribute('baseFrequency', fx.toFixed(5) + ' ' + fy.toFixed(5));
        }
      }
      frame(1800);
      if (reduced()) return;

      var clock = 1800, prev = 0;
      var ticker = anime.createTimer({
        loop: true,
        duration: 1000,
        autoplay: false,
        onUpdate: function () {
          var now = performance.now();
          clock += prev ? Math.min(50, now - prev) : 16;
          prev = now;
          frame(clock);
        }
      });

      new IntersectionObserver(function (entries) {
        if (entries[0].isIntersecting) { prev = 0; ticker.play(); }
        else ticker.pause();
      }).observe(el);
    }

    function init() {
      document.querySelectorAll('[data-liquid-chrome-text]').forEach(function (el) {
        if (el._lqc) return;
        el._lqc = true;
        setup(el);
      });
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<h1 data-liquid-chrome-text>Liquid metal</h1>

<!-- molten gold: -->
<h1 data-liquid-chrome-text data-metal="gold">Solid gold</h1>