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
- Paste the snippet just before
</head>. - Add
data-skeleton-shimmerto the placeholder’s container. - Mark each block inside with
data-sk(ordata-sk="circle") and give it a width and height.
Options
Add any of these attributes to the same element.
data-color | Block colour. Default #e8e6ef. |
data-shine | Sheen colour. Default #f7f6fb. |
data-speed | Time 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.
<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><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>