← Backgrounds

Glowing Orb

A soft glowing orb floats behind a section, slowly breathing in and out while it wanders about, its colours swirling gently inside it.

Meet your assistant

How to use it

  1. Paste the snippet just before </head>.
  2. Add data-glowing-orb to a section with a dark background.

Options

Add any of these attributes to the same element.

data-colorMain colour. Default violet #a855f7.
data-color2The colour swirling through it. Default pink #ec4899.
data-sizeDiameter in px. Default 60% of the section’s shorter side.
data-breathSeconds per breath. Default 5.
data-driftHow far it wanders, from 0 (stays put) to 1. Default 0.6.

It’s a few blurred layers moved with transforms, so it’s smooth and light, and it pauses when off screen. With reduced motion it sits still in the middle.

1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Glowing Orb: a soft glowing orb floats behind a section, slowly
    // breathing in and out while it drifts about and its colours swirl inside
    // it. Built from a few blurred layers, moved on the GPU, and paused when
    // off screen.
    // Usage: <section data-glowing-orb style="background:#0c0717">…</section>
    // Optional: data-color (main colour, default violet #a855f7), data-color2
    // (the colour swirling through it, default pink #ec4899), data-size
    // (diameter in px, default 60% of the section's shorter side), data-breath
    // (seconds per breath, default 5), data-drift (how far it wanders, from 0
    // to 1, default 0.6).
    var DEFAULTS = { color: '#a855f7', color2: '#ec4899', breath: 5, drift: 0.6 };

    var css =
      '[data-glowing-orb]{position:relative;isolation:isolate;overflow:hidden}' +
      '.gor-layer{position:absolute;inset:0;z-index:-1;pointer-events:none;overflow:hidden}' +
      '.gor-orb{position:absolute;left:50%;top:50%;will-change:transform}' +
      '.gor-orb>span{position:absolute;inset:0;border-radius:50%}' +
      '.gor-halo{transform:scale(2.1)}' +
      '.gor-swirl{will-change:transform}';
    var style = document.createElement('style');
    style.textContent = css;
    document.head.appendChild(style);

    function build(el) {
      var d = el.dataset;
      var c1 = d.color || DEFAULTS.color, c2 = d.color2 || DEFAULTS.color2;
      var breath = (+d.breath || DEFAULTS.breath) * 1000;
      var drift = d.drift !== undefined ? +d.drift : DEFAULTS.drift;
      var layer = document.createElement('div');
      layer.className = 'gor-layer';
      layer.setAttribute('aria-hidden', 'true');
      layer.innerHTML = '<div class="gor-orb"><span class="gor-halo"></span><span class="gor-swirl"></span><span class="gor-core"></span></div>';
      el.insertBefore(layer, el.firstChild);
      var orb = layer.firstChild, halo = orb.children[0], swirl = orb.children[1], core = orb.children[2];
      halo.style.background = 'radial-gradient(circle,' + c1 + ' 0%,transparent 62%)';
      swirl.style.background =
        'radial-gradient(circle at 34% 38%,' + c2 + ' 0%,transparent 52%),' +
        'radial-gradient(circle at 66% 62%,' + c1 + ' 0%,transparent 58%),' +
        'radial-gradient(circle,' + c1 + ' 30%,transparent 70%)';
      core.style.background = 'radial-gradient(circle at 46% 44%,rgba(255,255,255,.95) 0%,rgba(255,255,255,.35) 22%,transparent 46%)';
      var w = 0, h = 0, size = 0, t = 0;

      function resize() {
        w = el.clientWidth;
        h = el.clientHeight;
        size = +d.size || Math.max(90, Math.min(460, Math.min(w, h) * 0.6));
        orb.style.width = orb.style.height = size + 'px';
        orb.style.margin = (-size / 2) + 'px 0 0 ' + (-size / 2) + 'px';
        halo.style.filter = 'blur(' + Math.round(size * 0.12) + 'px)';
        swirl.style.filter = 'blur(' + Math.round(size * 0.07) + 'px)';
        core.style.filter = 'blur(' + Math.round(size * 0.04) + 'px)';
        place();
      }

      function place() {
        // Gentle, never-repeating wander from a few slow sine waves.
        var ax = Math.max(w * 0.1, (w - size) / 2) * drift, ay = Math.max(h * 0.08, (h - size) / 2) * drift;
        var x = ax * (0.65 * Math.sin(t * 0.21) + 0.35 * Math.sin(t * 0.13 + 1.3));
        var y = ay * (0.6 * Math.sin(t * 0.17 + 0.6) + 0.4 * Math.sin(t * 0.29 + 2.1));
        var b = Math.sin((t * 1000 / breath) * Math.PI * 2);
        orb.style.transform = 'translate(' + x.toFixed(1) + 'px,' + y.toFixed(1) + 'px) scale(' + (1 + b * 0.08).toFixed(4) + ')';
        halo.style.opacity = (0.75 + b * 0.25).toFixed(3);
        core.style.opacity = (0.8 + b * 0.2).toFixed(3);
        swirl.style.transform = 'rotate(' + (t * 22).toFixed(1) + 'deg)';
      }

      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;
          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-glowing-orb]').forEach(function (el) {
        if (el._gor) return;
        el._gor = true;
        build(el);
      });
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<section data-glowing-orb style="background: #0c0717; color: #fff">
  <h1>Meet your assistant</h1>
</section>