(function () { if (window.__LAZY_LOADER_READY__) return; const hasIO = "IntersectionObserver" in window; // 统一拿 fallback(支持:data-fallback > data-placeholder > window.__LAZY_PLACEHOLDER__ > 当前 src) function getFallback(el) { return ( el.getAttribute("data-fallback") || el.getAttribute("data-placeholder") || window.__LAZY_PLACEHOLDER__ || el.getAttribute("src") || "" ); } // 背景图预加载(可选:避免 background-image 失败无感知) function preloadImage(url, onOk, onFail) { const img = new Image(); img.onload = function () { onOk && onOk(); }; img.onerror = function () { onFail && onFail(); }; img.src = url; } function applyBg(el) { const src = el.getAttribute("data-src"); if (!src) return; // 如果你不需要 bg 失败兜底,可把 preloadImage 这段改成直接 set backgroundImage preloadImage( src, function () { el.style.backgroundImage = `url(${src})`; el.classList.remove("lazyload-bg"); el.removeAttribute("data-src"); el.classList.add("is-bg-loaded"); }, function () { // bg 回退:如果你不给 data-fallback / window.__LAZY_PLACEHOLDER__,这里就不处理 const fallback = getFallback(el); if (fallback) { el.style.backgroundImage = `url(${fallback})`; el.classList.add("is-bg-fallback"); } el.classList.remove("lazyload-bg"); el.removeAttribute("data-src"); } ); } function applyImg(img) { const src = img.getAttribute("data-src"); const srcset = img.getAttribute("data-srcset"); const sizes = img.getAttribute("data-sizes"); if (!src && !srcset) return; const fallback = getFallback(img); let done = false; // 必须在赋值 src/srcset 之前绑定,才能抓到加载失败 img.onerror = function () { if (done) return; done = true; // 防止 fallback 也报错导致死循环 img.onerror = null; img.onload = null; if (fallback) img.setAttribute("src", fallback); img.removeAttribute("srcset"); img.removeAttribute("sizes"); img.classList.add("is-img-fallback"); // 清理 lazy 属性,避免重复触发 img.classList.remove("lazyload-img"); img.removeAttribute("data-src"); img.removeAttribute("data-srcset"); img.removeAttribute("data-sizes"); }; img.onload = function () { if (done) return; done = true; img.onerror = null; img.onload = null; img.classList.add("is-img-loaded"); // 清理 lazy 属性 img.classList.remove("lazyload-img"); img.removeAttribute("data-src"); img.removeAttribute("data-srcset"); img.removeAttribute("data-sizes"); }; // 先设置响应式属性 if (sizes) img.setAttribute("sizes", sizes); if (srcset) img.setAttribute("srcset", srcset); // 最后触发真实加载 if (src) img.setAttribute("src", src); // 注意:这里不立刻 remove lazy class/attrs,等 onload/onerror 决定 // 否则失败后你很难判断是否已经处理过 } function observe(elements, callback, options) { if (!elements || elements.length === 0) return; // 兜底:不支持 IO 直接加载 if (!hasIO) { elements.forEach(callback); return; } const obs = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (!entry.isIntersecting) return; callback(entry.target); observer.unobserve(entry.target); }); }, options); elements.forEach(el => obs.observe(el)); } function scan(root) { const ctx = root || document; observe( ctx.querySelectorAll(".lazyload-bg"), applyBg, { rootMargin: "50px 0px", threshold: 0 } ); observe( ctx.querySelectorAll(".lazyload-img"), applyImg, { rootMargin: "0px 0px", threshold: 0 } ); } document.addEventListener("DOMContentLoaded", function () { scan(document); }); // 给“加载更多/分页追加 DOM”使用 window.lazyScan = scan; window.__LAZY_LOADER_READY__ = true; })();