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
- Paste the snippet just before
</head>. - Add
data-conic-glow-spinto a section with a dark background.
Options
Add any of these attributes to the same element.
data-colors | Colours round the circle, comma-separated. 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 | Strength, 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.
<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><section data-conic-glow-spin style="background: #09090f; color: #fff">
<div class="card">Now in colour</div>
</section>