Created
May 10, 2026 03:19
-
-
Save toomanyredirects/00e74ddd1bd2288171d1ab7a646b7a55 to your computer and use it in GitHub Desktop.
Universal Lazy Image Spinner + Failure Fallback (Legacy‑Safe, No‑JS‑Safe)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** 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 | |
| }); | |
| } | |
| })(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment