概念
图片懒加载,就是在一开始的时候,收集需要做懒加载的图片元素集合。这些图片一般一开始都没有 src 属性,而是只有一个 data-src 自定义属性。对于这个图片元素的集合,通过判断哪些在视口内,哪些在视口外。在视口内的,就给它们的 src 属性赋值。
设置为"data-*"的格式,是因为只有这种格式才能在DOM中的dataset属性轻松取到,比如:data-name="zhang",在对应的DOM中其dataset为 { name: 'zhang' }。
根据现代浏览器的支持程度不同,这里可以有两种解决方案:
- 针对现代浏览器,使用强大的
IntersectionObserver
(交叉观察器),不限容器、不限事件 - 针对 IE 浏览器,以及一些版本古老的浏览器,使用
getBouningClientRect
方法+scroll
事件+throttle
节流器。
其中第二种降级方案的缺点是不言而喻的,比如:
- 容器受限:当父级容器很小时,超出父级容器的图片通过 getBoundingClientRect 拿到的 top 小于视口高度,但是其实此时这张图片是不可见的。
- 事件受限:如需应对不同的事件(滚动、缩放等),只能一个个添加事件监听器。
现代版本,强大的 IntersectionObserver
这个东西着实牛逼,只要给相关元素添加了观察器,无论是任何容器、任何事件、任何场合都能准确地判断用户“能不能看到它”,从 chrome51+版本就开始支持了。
它的使用流程是这样的:
- 实例化一个观察者对象,在实例化时传入可见性变化的回调作为参数;
- 通过
.obeserve
方法给 DOM 元素添加可见性监听器; - 通过
.unobserve
方法移出对相应 DOM 元素的可见性监听。
使用示例如下:
// 现代浏览器版本
const imgArr = Array.from(document.querySelectorAll("img"));
const io = new IntersectionObserver((entries) => {
// entries表示可见性发生变化的DOM元素合集,比如首屏一进来就有三张图片,它们同时由“不可见”变为“可见”
entries.forEach((entry) => {
if (entry.isIntersecting) {
// isIntersecting为true,就表示元素可见
const target = entry.target;
target.src = target.dataset.src;
io.unobserve(target);
}
});
});
imgArr.forEach((img) => io.observe(img));
具体的使用包括一些细节,如果不记得可以翻阮一峰的教程。IntersectionObserver API 使用教程
古老版本,一种降级方案
古老版本中,用于判断元素是否可见,是通过getClientBoundingRect
来做的。getClientBoundingRect 返回的 top 值,是相对于屏幕视窗左上角来定位的。初略地认为: 当这个 top 值 < window.innerHeight 时,就是可见的。
再把这种动态判断逻辑放在滚动事件触发的函数中,为了节省性能,滚动的回调一般用 throttle 节流器包装一层,代码如下:
// 兼容IE及其他古老浏览器版本
const throttle = function (func, wait) {
let prev = Date.now();
return function (...args) {
const now = Date.now();
const context = this;
if (now - prev >= wait) {
func.apply(context, args);
prev = Date.now();
}
};
};
const imgArr = Array.from(document.querySelectorAll("img"));
const lazyLoad = function () {
const viewportHeight = window.innerHeight;
imgArr.forEach((img) => {
const top = img.getBoundingClientRect().top;
if (top < viewportHeight && !img.src) {
img.src = img.dataset.src;
}
});
};
const tLazyLoad = throttle(lazyLoad, 200);
lazyLoad(); // 这里要先执行一次,因为第一次进来虽然没有滚动,但是也要判断哪些图片可见,并赋上src
document.addEventListener("scroll", tLazyLoad);
总结
综合来看,交叉观察器要准确很多,目前已经被绝大部分现代浏览器支持,如果要做完美兼容,可以先通过"IntersectionObeserver" in window
来判断一下window对象有没有这个属性,有就用它,没有就降级处理。