← Backgrounds

Conic Glow Spin

A soft ring of colour glows behind your content and turns slowly, so violet, pink, amber and cyan sweep round it in turn. A fainter glow turns the other way inside it, so it never looks mechanical.

Now in colour

How to use it

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

Options

Add any of these attributes to the same element.

data-colorsColours round the circle, comma-separated. Default violet, pink, amber and cyan.
data-durationSeconds per turn. Default 12.
data-sizeDiameter in px. Default 85% of the section’s shorter side.
data-blurSoftness in px. Default a tenth of the size.
data-opacityStrength, from 0 to 1. Default 0.9.

It’s a CSS conic-gradient, blurred and turned with transforms, so it costs next to nothing; 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 () {
    // Conic Glow Spin: a soft, blurred conic gradient turns slowly behind the
    // content, so the colours sweep round it like light round a lens. A second,
    // fainter glow turns the other way inside it to keep it from looking
    // mechanical. Paused when off screen.
    // Usage: <section data-conic-glow-spin style="background:#09090f">…</section>
    // Optional: data-colors (comma-separated, round the circle, default violet,
    // pink, amber and cyan), data-duration (seconds per turn, default 12),
    // data-size (diameter in px, default 85% of the section's shorter side),
    // data-blur (softness in px, default a tenth of the size), data-opacity
    // (from 0 to 1, default 0.9).
    var DEFAULTS = { colors: '#7c3aed,#ec4899,#f59e0b,#22d3ee', duration: 12, opacity: 0.9 };

    var css =
      '[data-conic-glow-spin]{position:relative;isolation:isolate;overflow:hidden}' +
      '.cgs-layer{position:absolute;inset:0;z-index:-1;pointer-events:none;overflow:hidden}' +
      '.cgs-glow{position:absolute;left:50%;top:50%;border-radius:50%;will-change:transform;' +
      '-webkit-mask:radial-gradient(closest-side,#000 35%,transparent);mask:radial-gradient(closest-side,#000 35%,transparent)}';
    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(',').map(function (c) { return c.trim(); });
      var turn = (+d.duration || DEFAULTS.duration) * 1000;
      var opacity = d.opacity !== undefined ? +d.opacity : DEFAULTS.opacity;
      var layer = document.createElement('div');
      layer.className = 'cgs-layer';
      layer.setAttribute('aria-hidden', 'true');
      layer.innerHTML = '<div class="cgs-glow"></div><div class="cgs-glow"></div>';
      el.insertBefore(layer, el.firstChild);
      var outer = layer.children[0], inner = layer.children[1];
      // Close the circle on the first colour so there is no seam.
      var ring = colors.concat(colors[0]).join(',');
      outer.style.background = 'conic-gradient(from 0deg,' + ring + ')';
      inner.style.background = 'conic-gradient(from 180deg,' + colors.slice().reverse().concat(colors[colors.length - 1]).join(',') + ')';
      outer.style.opacity = opacity;
      inner.style.opacity = opacity * 0.55;

      function resize() {
        var size = +d.size || Math.max(120, Math.min(el.clientWidth, el.clientHeight) * 0.85);
        var blur = d.blur !== undefined ? +d.blur : Math.round(size / 10);
        [[outer, 1], [inner, 0.6]].forEach(function (g) {
          var s = size * g[1];
          g[0].style.width = g[0].style.height = s + 'px';
          g[0].style.margin = -s / 2 + 'px 0 0 ' + -s / 2 + 'px';
          g[0].style.filter = 'blur(' + Math.round(blur * g[1]) + 'px)';
        });
      }

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

      var spins = [
        anime.animate(outer, { rotate: [0, 360], duration: turn, ease: 'linear', loop: true, autoplay: false }),
        anime.animate(inner, { rotate: [0, -360], duration: turn * 1.6, ease: 'linear', loop: true, autoplay: false })
      ];
      new IntersectionObserver(function (entries) {
        var on = entries[0].isIntersecting;
        spins.forEach(function (a) { if (on) a.play(); else a.pause(); });
      }).observe(el);
    }

    function init() {
      document.querySelectorAll('[data-conic-glow-spin]').forEach(function (el) {
        if (el._cgs) return;
        el._cgs = true;
        build(el);
      });
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<section data-conic-glow-spin style="background: #09090f; color: #fff">
  <div class="card">Now in colour</div>
</section>