Files
SEONexus/code/public/static/js/modules/lazy.js
2026-01-09 01:28:23 +08:00

155 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(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;
})();