← Backgrounds

Glass Blobs

Soft blobs of frosted glass drift and wobble across a section while bold shapes of colour slide past behind them. Wherever the glass passes over a shape it blurs, brightens and bends it, with a glint of light on each blob’s shoulder.

Clear thinking

How to use it

  1. Paste the snippet just before </head>.
  2. Add data-glass-blobs to a section.

Options

Add any of these attributes to the same element.

data-colorsThe shapes behind the glass, comma-separated. Default violet, pink, cyan and amber.
data-countHow many glass blobs. Default 3 to 5, depending on the section’s size.
data-blurHow frosted the glass is, in px. Default 14.
data-speedSpeed multiplier. Default 1.
data-backdrop="false"No shapes behind: the glass frosts your own background image instead.

The glass uses backdrop-filter and everything moves with transforms, so it stays smooth; it pauses when off screen. With reduced motion it holds still.

1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Glass Blobs: soft blobs of frosted glass drift and wobble across a
    // section, blurring and brightening the bold colours that slide past
    // behind them, like pebbles of glass over a painted wall. Built from a few
    // layers moved on the GPU, and paused when off screen.
    // Usage: <section data-glass-blobs style="background:#1e1b4b">…</section>
    // Optional: data-colors (the shapes behind the glass, comma-separated,
    // default violet, pink, cyan and amber), data-count (how many glass blobs,
    // default 3 to 5 depending on size), data-blur (how frosted the glass is,
    // in px, default 14), data-speed (default 1), data-backdrop="false" (no
    // shapes behind; the glass refracts your own background instead).
    var DEFAULTS = { colors: '#8b5cf6,#ec4899,#22d3ee,#f59e0b', blur: 14, speed: 1 };

    var css =
      '[data-glass-blobs]{position:relative;isolation:isolate;overflow:hidden}' +
      '.gls-layer{position:absolute;inset:0;z-index:-1;pointer-events:none;overflow:hidden}' +
      '.gls-shape,.gls-glass{position:absolute;left:0;top:0;will-change:transform}' +
      '.gls-shape{border-radius:50%}' +
      '.gls-glass{overflow:hidden;background:linear-gradient(140deg,rgba(255,255,255,.34),rgba(255,255,255,.06) 55%,rgba(255,255,255,.14));' +
      'border:1px solid rgba(255,255,255,.4);box-shadow:inset 0 2px 14px rgba(255,255,255,.45),inset 0 -14px 34px rgba(20,10,60,.18),0 18px 40px rgba(10,5,30,.22)}' +
      '.gls-glass::after{content:"";position:absolute;left:16%;top:9%;width:38%;height:22%;border-radius:50%;' +
      'background:radial-gradient(closest-side,rgba(255,255,255,.75),rgba(255,255,255,0));transform:rotate(-24deg)}';
    var style = document.createElement('style');
    style.textContent = css;
    document.head.appendChild(style);

    function build(el) {
      var d = el.dataset;
      var colors = (d.colors || DEFAULTS.colors).split(',');
      var blur = d.blur !== undefined ? +d.blur : DEFAULTS.blur;
      var speed = d.speed !== undefined ? +d.speed : DEFAULTS.speed;
      var backdrop = d.backdrop !== 'false';
      var layer = document.createElement('div');
      layer.className = 'gls-layer';
      layer.setAttribute('aria-hidden', 'true');
      el.insertBefore(layer, el.firstChild);
      var R = anime.utils.random;
      var shapes = [], blobs = [], w = 0, h = 0, t = R(0, 100);

      // Crisp coloured shapes behind, so the glass has something to bend.
      if (backdrop) {
        for (var i = 0; i < Math.max(4, colors.length + 1); i++) {
          var s = document.createElement('div');
          s.className = 'gls-shape';
          s.style.background = colors[i % colors.length].trim();
          if (i % 3 === 2) s.style.borderRadius = '28%';
          layer.appendChild(s);
          shapes.push({ el: s, k: R(40, 75) / 100, fx: R(5, 12) / 100, fy: R(5, 12) / 100, p: R(0, 628) / 100, q: R(0, 628) / 100, ox: (i + 0.5) / Math.max(4, colors.length + 1) });
        }
      }

      function make(n) {
        blobs.forEach(function (b) { b.el.remove(); });
        blobs = [];
        for (var i = 0; i < n; i++) {
          var g = document.createElement('div');
          g.className = 'gls-glass';
          var f = 'blur(' + blur + 'px) saturate(1.7) brightness(1.12)';
          g.style.backdropFilter = f;
          g.style.webkitBackdropFilter = f;
          layer.appendChild(g);
          blobs.push({ el: g, k: R(55, 100) / 100, fx: R(6, 13) / 100, fy: R(6, 13) / 100, p: R(0, 628) / 100, q: R(0, 628) / 100, m: R(0, 628) / 100, ox: (i + 0.5) / n });
        }
      }

      function resize() {
        w = el.clientWidth;
        h = el.clientHeight;
        var n = +d.count || Math.max(3, Math.min(5, Math.round((w * h) / 90000) + 2));
        if (n !== blobs.length) make(n);
        var m = Math.min(w, h);
        shapes.forEach(function (s) { s.size = m * s.k; s.el.style.width = s.el.style.height = s.size + 'px'; });
        blobs.forEach(function (b) { b.size = Math.max(70, Math.min(320, m * 0.48 * b.k)); b.el.style.width = b.el.style.height = b.size + 'px'; });
        place();
      }

      // Each piece wanders on slow sine waves around its own lane.
      function move(p, z) {
        var x = p.ox * w + Math.sin(t * p.fx * z + p.p) * w * 0.32 - p.size / 2;
        var y = h * 0.5 + Math.sin(t * p.fy * z + p.q) * (h * 0.5 + p.size * 0.1) - p.size / 2;
        return 'translate(' + x.toFixed(1) + 'px,' + y.toFixed(1) + 'px)';
      }

      function place() {
        shapes.forEach(function (s) { s.el.style.transform = move(s, 0.7) + ' rotate(' + (t * 6 * s.fx * 10 + s.p * 40).toFixed(1) + 'deg)'; });
        blobs.forEach(function (b) {
          b.el.style.transform = move(b, 1);
          // Wobble the outline so each blob looks soft, not a plain circle.
          var a = [];
          for (var i = 0; i < 8; i++) a.push((50 + 13 * Math.sin(t * 0.55 + b.m + i * 1.7)).toFixed(1) + '%');
          b.el.style.borderRadius = a[0] + ' ' + a[1] + ' ' + a[2] + ' ' + a[3] + ' / ' + a[4] + ' ' + a[5] + ' ' + a[6] + ' ' + a[7];
        });
      }

      resize();
      if (window.ResizeObserver) new ResizeObserver(resize).observe(el);
      if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;

      var last = 0;
      var timer = anime.createTimer({
        loop: true,
        duration: 1000,
        autoplay: false,
        onUpdate: function () {
          var now = performance.now();
          t += ((last ? Math.min(50, now - last) : 16) / 1000) * speed;
          last = now;
          place();
        }
      });
      new IntersectionObserver(function (entries) {
        if (entries[0].isIntersecting) { last = 0; timer.play(); } else timer.pause();
      }).observe(el);
    }

    function init() {
      document.querySelectorAll('[data-glass-blobs]').forEach(function (el) {
        if (el._gls) return;
        el._gls = true;
        build(el);
      });
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<section data-glass-blobs style="background: #1e1b4b; color: #fff">
  <h1>Clear thinking</h1>
</section>