← Loaders

Skeleton Shimmer

Grey placeholder blocks shaped like the content that’s on its way, with a soft sheen rolling across them, block after block, until it arrives.

How to use it

  1. Paste the snippet just before </head>.
  2. Add data-skeleton-shimmer to the placeholder’s container.
  3. Mark each block inside with data-sk (or data-sk="circle") and give it a width and height.

Options

Add any of these attributes to the same element.

data-colorBlock colour. Default #e8e6ef.
data-shineSheen colour. Default #f7f6fb.
data-speedTime for one sweep. Default 1500 ms.

Blocks are 0.9em tall unless you set a height. The container is marked aria-busy for screen readers. Remove it, or swap in your content, when the real thing arrives.

1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Skeleton Shimmer: grey placeholder blocks with a soft sheen sweeping
    // across them, rolling from block to block, while real content loads.
    // Usage:
    //   <div data-skeleton-shimmer>
    //     <div data-sk="circle" style="width:48px;height:48px"></div>
    //     <div data-sk style="width:60%"></div>
    //     <div data-sk style="width:90%"></div>
    //   </div>
    // Every [data-sk] inside becomes a block (data-sk="circle" for a round one);
    // with none inside, the element itself is the block. Blocks are 0.9em tall
    // unless you give them a height.
    // Optional: data-color (block colour, default #e8e6ef), data-shine (sheen
    // colour, default #f7f6fb), data-speed (ms per sweep, default 1500).
    // Remove the placeholder, or swap in your content, when it arrives.
    var DEFAULTS = { color: '#e8e6ef', shine: '#f7f6fb', speed: 1500 };

    var css =
      '.skx{display:block;min-height:0.9em;border-radius:0.45em;background-repeat:no-repeat;background-size:300% 100%;background-position:100% 0}' +
      '.skx[data-sk="circle"]{border-radius:50%}';
    var style = document.createElement('style');
    style.textContent = css;
    document.head.appendChild(style);

    function setup(el) {
      var d = el.dataset;
      var blocks = Array.from(el.querySelectorAll('[data-sk]'));
      if (!blocks.length) blocks = [el];
      var color = d.color || DEFAULTS.color, shine = d.shine || DEFAULTS.shine;
      el.setAttribute('aria-busy', 'true');
      if (!el.getAttribute('aria-label')) el.setAttribute('aria-label', 'Loading');
      blocks.forEach(function (b) {
        b.classList.add('skx');
        b.style.backgroundColor = color;
        b.style.backgroundImage = 'linear-gradient(100deg,' + color + ' 40%,' + shine + ' 50%,' + color + ' 60%)';
      });
      if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
      var anim = anime.animate(blocks, {
        backgroundPosition: ['100% 0', '0% 0'],
        duration: +d.speed || DEFAULTS.speed,
        ease: 'inOut(2)',
        loop: true,
        // Each block starts a beat after the one before, so the sheen rolls down.
        delay: anime.stagger(90)
      });
      new IntersectionObserver(function (entries) {
        if (entries[0].isIntersecting) anim.play(); else anim.pause();
      }).observe(el);
    }

    function init() {
      document.querySelectorAll('[data-skeleton-shimmer]').forEach(function (el) {
        if (el._skx) return;
        el._skx = true;
        setup(el);
      });
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<div data-skeleton-shimmer>
  <div data-sk="circle" style="width:48px;height:48px"></div>
  <div data-sk style="width:60%"></div>
  <div data-sk style="width:90%"></div>
</div>