155 lines
4.7 KiB
JavaScript
155 lines
4.7 KiB
JavaScript
(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;
|
||
})();
|