← Loaders

Orbit Loader

A few dots chase each other round a circle, bunching up as they speed up and spreading out as they slow. Size it with font-size and colour it with color.

How to use it

  1. Paste the snippet just before </head>.
  2. Add an empty <div data-orbit-loader> where the loader should appear.

Options

Add any of these attributes to the same element.

data-dotsNumber of dots, 2 to 8. Default 5.
data-speedTime for one lap. Default 1500 ms.
1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Orbit Loader: a few dots chase each other round a circle, bunching up and
    // spreading out as they speed up and slow down. Sized with font-size,
    // coloured with color.
    // Usage: <div data-orbit-loader aria-label="Loading"></div>
    // Optional: data-dots (default 5), data-speed (ms per lap, default 1500).
    var DEFAULTS = { dots: 5, speed: 1500 };

    var css =
      '.orl{position:relative;display:inline-block;width:2.4em;height:2.4em}' +
      '.orl-arm{position:absolute;inset:0}' +
      '.orl-dot{position:absolute;left:50%;top:0;width:0.36em;height:0.36em;margin-left:-0.18em;border-radius:50%;background:currentColor}';
    var style = document.createElement('style');
    style.textContent = css;
    document.head.appendChild(style);

    function build(el) {
      var d = el.dataset;
      var n = Math.max(2, Math.min(8, +d.dots || DEFAULTS.dots));
      el.classList.add('orl');
      el.setAttribute('role', 'progressbar');
      if (!el.getAttribute('aria-label')) el.setAttribute('aria-label', 'Loading');
      el.textContent = '';
      var arms = [];
      for (var i = 0; i < n; i++) {
        var arm = document.createElement('span');
        arm.className = 'orl-arm';
        var dot = document.createElement('span');
        dot.className = 'orl-dot';
        // Leading dots are bigger; the tail fades off.
        dot.style.transform = 'scale(' + (1 - i * (0.5 / n)) + ')';
        dot.style.opacity = 1 - i * (0.6 / n);
        arm.appendChild(dot);
        el.appendChild(arm);
        arms.push(arm);
      }
      if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
        arms.forEach(function (a, i) { a.style.transform = 'rotate(' + -i * 24 + 'deg)'; });
        return;
      }
      // Every arm laps with the same easing but starts a little later, so the
      // dots bunch as they accelerate and spread as they slow.
      anime.animate(arms, {
        rotate: [0, 360],
        duration: +d.speed || DEFAULTS.speed,
        delay: anime.stagger(70),
        loopDelay: 0,
        ease: 'inOut(2.2)',
        loop: true
      });
    }

    function init() {
      document.querySelectorAll('[data-orbit-loader]').forEach(function (el) {
        if (el._orl) return;
        el._orl = true;
        build(el);
      });
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<div data-orbit-loader style="font-size: 24px; color: #6a0dad"></div>