Skip to content

Instantly share code, notes, and snippets.

@toomanyredirects
Created May 10, 2026 03:19
Show Gist options
  • Select an option

  • Save toomanyredirects/00e74ddd1bd2288171d1ab7a646b7a55 to your computer and use it in GitHub Desktop.

Select an option

Save toomanyredirects/00e74ddd1bd2288171d1ab7a646b7a55 to your computer and use it in GitHub Desktop.
Universal Lazy Image Spinner + Failure Fallback (Legacy‑Safe, No‑JS‑Safe)
<img loading="lazy" src="none.jpg" src_="https://thumbnails.production.thenounproject.com/w1MQqqJPo_-KN9kP52SigeMHwJE=/fit-in/1000x1000/photos.production.thenounproject.com/photos/12ce5d48-3ab2-46d7-ad05-27279c21a58b.jpg" alt="" aria-label="image" width="400" height="300"/>
<p>
This demo shows the <strong>Universal Lazy Image Spinner + Failure Fallback</strong>
a fully framework‑agnostic, legacy‑safe, no‑JS‑safe image loader. It displays a
lightweight SVG spinner while loading and a clean failure icon with a background
fade‑in when the image cannot be loaded. No wrappers, no pseudo‑elements, no modern
CSS, and no layout shifts.
</p>
/** Lazy Image Loading Spinner */
// Show a loading spinner on <img/> elements and add own loading error.
(function loadingLazySpinner () {
var imgs = document.querySelectorAll('img[loading="lazy"], .loading-lazy');
function loaded () {
this.classList.add('loaded');
}
function failed () {
this.removeAttribute('alt');
this.classList.add('failed');
this.src = 'data:image/gif;base64,R0lGODlhAQABAPAAAP///wAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==';
}
function attach (img) {
if (img._lazyBound) return;
img._lazyBound = true;
img.addEventListener('load', loaded);
img.addEventListener('error', failed);
}
// Initial static pass
for (var i = 0; i < imgs.length; i++) attach(imgs[i]);
// Dynamic DOM support (MutationObserver)
if (window.MutationObserver) {
new MutationObserver (function (mutations) {
for (var m = 0; m < mutations.length; m++) {
var nodes = mutations[m].addedNodes;
for (var n = 0; n < nodes.length; n++) {
var node = nodes[n];
// Node is an <img/>
if (node.tagName === 'IMG' && (node.loading === 'lazy' || node.classList.contains('loading-lazy'))) {
attach(node);
}
// Node contains <img/> elements
if (node.querySelectorAll) {
var found = node.querySelectorAll('img[loading="lazy"], .loading-lazy');
for (var f = 0; f < found.length; f++) attach(found[f]);
}
}
}
}).observe(document.documentElement, {
childList: true,
subtree: true
});
}
})();
// Demo Display Only
body {
font-family: arial, sans-serif;
padding: 1rem;
}
// Lazy Image Loading Spinner
$lazyload-bgcolor: rgba(128, 128, 128, 0.1);
$lazyload-bgcolor-encoded: "%2380808018";
$lazyload-spinner: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cstyle%3E.a{transform-origin:4px 4px;animation:r 1s linear infinite,a 800ms linear infinite}@keyframes r{to{transform:rotate(360deg)}}@keyframes a{50%{stroke-dasharray:9 18}}%3C/style%3E%3Ccircle cx='4' cy='4' r='3' fill='none' stroke='#{$lazyload-bgcolor-encoded}' stroke-width='1'/%3E%3Ccircle class='a' cx='4' cy='4' r='3' fill='none' stroke='currentColor' stroke-width='0.5' stroke-linecap='square' stroke-dasharray='3 24'/%3E%3C/svg%3E";
$lazyload-failedicon: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cstyle%3E.l{stroke-dasharray:4.2;stroke-dashoffset:4.2;animation:draw 400ms linear forwards}.l2{animation-delay:200ms}@keyframes draw{to{stroke-dashoffset:0}}%3C/style%3E%3Ccircle cx='4' cy='4' r='3' fill='none' stroke='currentColor' stroke-width='0.5'/%3E%3Cline class='l' x1='2.8' y1='2.8' x2='5.2' y2='5.2' stroke='currentColor' stroke-width='0.5' stroke-linecap='round'/%3E%3Cline class='l l2' x1='5.2' y1='2.8' x2='2.8' y2='5.2' stroke='currentColor' stroke-width='0.5' stroke-linecap='square'/%3E%3C/svg%3E";
img[loading='lazy'], .loading-lazy {
max-width: 100%;
max-height: 100%;
object-fit: contain;
background: transparent url($lazyload-spinner) center / 2rem no-repeat;
&.loaded {
background: none;
max-width: none;
max-height: none;
}
&.failed {
object-fit: none;
cursor: not-allowed;
background: $lazyload-bgcolor url($lazyload-failedicon) center / 2rem no-repeat;
animation: loading-lazy-bgfade 150ms linear;
}
}
@keyframes loading-lazy-bgfade {
from { background-color: transparent; }
to { background-color: $lazyload-bgcolor; }
}
// IE9–10 fallback: force background to behave like object-fit:none
@media screen and (min-width:0\0) {
img[loading='lazy'].failed, .loading-lazy.failed {
width: 100%;
height: 100%;
background-size: 2rem 2rem;
background-repeat: no-repeat;
background-position: center center;
}
}

Universal Lazy Image Spinner + Failure Fallback (Legacy‑Safe, No‑JS‑Safe)

A Pen by Dan on CodePen.

License.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment