图片懒加载,即在图片出现在视口内或即将出现在视口内时再加载图片。图片懒加载可以分解为两个问题:
- 如何判断图片在视口内
- 如何控制加载图片
计算图片位置 + 滚动事件 + DataSet API
先设置一个临时的data属性的src占位
<img data-src="./images/01.jfif">
然后就可以下面的isShow()
函数判断该图片是否在视口中,即元素相对顶点的值<=
滚动的距离+
窗口的高度 [ +
预留的加载距离]
function isShow($node, preDistance) {
return $node.offset().top <= $(window).height() + $(window).scrollTop() + preDistance;
}
当图片出现(或即将出现)在视口,就可以控制图片开始加载,这里使用dataset
API
function lazyload($imgs) {
$imgs.each(function(idx, img) {
if (img.src) return;
if (isShow($(img), 100)) {
img.src = img.dataset.src;
}
})
}
在页面加载的同时就要先调用一次lazyload()
函数,让已经在视口中(首屏中)的图片开始加载,然后监听scroll
事件,并在事件监听器中调用lazyload()
函数。
lazyload($imgs);
window.onscroll = function (evt) {
lazyload($imgs);
}
getBoundingClientRect + 防抖 + DataSet API
这里引入Element.getBoundingClientRect()
方法,此方法返回一个DOMRect
对象,这个对象中提供了元素大小和相对于视口(左上角)的位置信息
如此,就可以不通过jQuery
来获取元素的位置了。
function isShow(img, preDistance) {
return img.getBoundingClientRect().top <= document.documentElement.clientHeight + preDistance;
}
同样的,在lazyload()
函数中用for
循环代替jQuery
元素的each()
方法。
function lazyload(imgs) {
for (let i = 0, len = imgs.length; i < len; i++) {
const img = imgs[i];
if (img.src) continue;
if (isShow(img, 100)) {
img.src = img.dataset.src;
}
}
}
由于滚动事件是高频事件,加个节流提高性能。一般使用lodash
就够了。
// 获取元素
const imgs = document.querySelectorAll('img');
// 首屏加载
lazyload(imgs);
function handleScroll() {
lazyload(imgs);
}
window.onscroll = _.throttle(handleScroll, 136);
IntersectionObserver API + DataSet API
以上两种方法都是固定套路:计算位置,加载图片,节流。于是浏览器新增了一个三合一的API:IntersectionObserver
(IE不支持)
IntersectionObserver
是一个类,实例化时传入一个回调函数作为构造函数参数。- 这个回调函数接收一个
changes
参数,是一个对象数组,包含一系列变化对象。 - 当
changes
数组中的某一个对象的isIntersecting
属性为true
时,表示被观察对象出现在视口中。这时可以写入图片加载程序,这里同样使用DataSet API
。 - 图片加载程序完成后,使用
observer.unobserve()
方法取消订阅change.target
对象,以防止重复加载。
const observer = new IntersectionObserver(changes => {
changes.forEach(change => {
if (change.isIntersecting) {
const item = change.target;
item.src = item.dataset.src;
observer.unobserve(item);
}
})
});
此对象实例化后,就可以使用observer.observe()
方法订阅所有需要懒加载的图片
const imgs = document.querySelectorAll('img');
imgs.forEach(img => {
observer.observe(img);
})
IntersectionObserver
除了给图片做懒加载外,还可以对单页应用资源做预加载。
LazyLoading属性
这应该是最简单的方式了,不过对前端而言,越简单代表兼容性越不好。
同时,使用loading="lazy"
后,并不是图片刚好进入视口时才加载,而是预留了一部分的加载距离。
<img src="./images/01.jfif" loading="lazy">