← Loaders

Grid Wave Loader

A grid of squares pulses in a wave that ripples out from the centre, over and over. 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-grid-wave-loader> where the loader should appear.

Options

Add any of these attributes to the same element.

data-sizeSquares per side. Default 4.
data-fromWhere the wave starts: center (default), first or last.
data-shape="circle"Use dots instead of squares.
data-speedTime for one pulse. Default 1100 ms.
1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Grid Wave Loader: a grid of squares pulses in a wave that ripples out from
    // the centre, over and over. Sized with font-size, coloured with color.
    // Usage: <div data-grid-wave-loader aria-label="Loading"></div>
    // Optional: data-size (squares per side, default 4), data-from ("center",
    // "first", "last"), data-shape="circle", data-speed (ms per pulse).
    var DEFAULTS = { size: 4, from: 'center', speed: 1100 };

    var css =
      '.gwl{display:inline-grid;gap:0.18em}' +
      '.gwl-cell{width:0.62em;height:0.62em;border-radius:0.14em;background:currentColor}' +
      '.gwl.is-circle .gwl-cell{border-radius:50%}';
    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(12, +d.size || DEFAULTS.size));
      el.classList.add('gwl');
      if (d.shape === 'circle') el.classList.add('is-circle');
      el.style.gridTemplateColumns = 'repeat(' + n + ', auto)';
      el.setAttribute('role', 'progressbar');
      if (!el.getAttribute('aria-label')) el.setAttribute('aria-label', 'Loading');
      el.textContent = '';
      var cells = [];
      for (var i = 0; i < n * n; i++) {
        var c = document.createElement('span');
        c.className = 'gwl-cell';
        el.appendChild(c);
        cells.push(c);
      }
      if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
      var speed = +d.speed || DEFAULTS.speed;
      anime.animate(cells, {
        scale: [{ to: 0.35, duration: speed * 0.5, ease: 'inOut(2)' }, { to: 1, duration: speed * 0.5, ease: 'inOut(2)' }],
        opacity: [{ to: 0.3, duration: speed * 0.5 }, { to: 1, duration: speed * 0.5 }],
        delay: anime.stagger(speed / 8, { grid: [n, n], from: d.from || DEFAULTS.from }),
        loop: true
      });
    }

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

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