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
- Paste the snippet just before
</head>. - Add
data-glowing-orbto a section with a dark background.
Options
Add any of these attributes to the same element.
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 (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.
<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><section data-glowing-orb style="background: #0c0717; color: #fff">
<h1>Meet your assistant</h1>
</section>