在一些图片比较多的网站会用到 图片懒加载 技术,这项技术可以延迟加载图像,只当图片出现在我们看到的视图中才加载,它的好处是大大提高用户体验,节省不必要的资源浪费以及网站的性能提升等。下面介绍几种图片懒加载的方法,分别是监听 scroll、resize 事件,使用 Intersection Observer API 以及 Chrome70 自带的懒加载设置。任何技术都是为解决问题服务的。在开始之前,还是要了解清楚「是什么」以及「为什么」。

什么是懒加载

当一个网站的图片数量较多时,直接加载可能会有很大的开销,不利于性能,这时可以将所有的图片换成轻量的占位图,不加载图片。而当用户真正滚动到图片出现时,再迅速将占位图片换成真正我们想展示的图片,这整个过程就是懒加载。

为什么要懒加载

当你打开一个网站时,浏览器会做许多工作,这其中包括下载各种可能用到的资源,然后渲染呈现在你面前,假设你的网站有大量的图片,那么加载的过程是很耗时的,尤其像那些新闻资讯类需要大量图片的网站,可想而知,网站的初始加载时间会很长,再加上网络等其它影响,用户体验会很差,相信你经常遇到过一个网站卡在某个地方,一直在加载,这种体验很不好。我们都希望一输入网址,页面立马就呈现在眼前。

既然想要页面立马呈现在面前,那势必要减少浏览器的负荷,优化代码,减少一些不必要的请求和不必要资源的加载,因为你打开网站的时候,浏览器会把所有可能的资源都下载好,而实际上有些资源你并不需要用到,这就造成了浪费。所以有必要在一些资源上做下优化,提高网站加载速度。

滚动事件监听

前面说到要等图片出现在视口时才加载,那么肯定要监控浏览器的 scroll 事件,并且要计算图片与浏览器窗口的距离来选择替换图片的 src 地址。代码如下:

1
2
3
4
5
6
7
8
9
HTML
<div>
<img class="lazy-load" data-src="https://source.unsplash.com/random/600" alt="">
<img class="lazy-load" data-src="https://source.unsplash.com/random/700" alt="">
<img class="lazy-load" data-src="https://source.unsplash.com/random/800" alt="">
<img class="lazy-load" data-src="https://source.unsplash.com/random/900" alt="">
</div>
// 引入 lodash 库
<script src="https://cdn.bootcss.com/lodash.js/4.17.12-pre/lodash.core.min.js"></script>

1
2
3
4
5
6
7
8
CSS
div {
margin-top: 350px;
}
.lazy-load {
width: 200px;
height: 150px;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
JS
let lazyImages = [...document.querySelectorAll('.lazy-load')]
let inAdvance = 300
function lazyLoad() {
lazyImages.forEach(image => {
if (image.offsetTop < window.innerHeight + window.pageYOffset + inAdvance) {
image.src = image.dataset.src; // 替换真实图片的 URL
}
})
}
lazyLoad();
window.addEventListener('scroll', _.throttle(lazyLoad, 50))
window.addEventListener('resize', _.throttle(lazyLoad, 50))

这其中有几个属性,首先是 data-src,它是自定义属性,可以在 js 里通过 dataset 获得它的属性值;还有 offsetTop ,innerHeight 以及 pageYOffset 属性,你可以通过 MDN 文档查询他们的定义和用法;最后是 _.throttle 函数,它是一个节流函数,引用自 lodash 库,因为监听 scroll 滚动以及 resize 窗口改变事件会不断地触发,过于频繁,所以使用节流函数让其每隔一段时间执行,节省开销。

Intersection Observer API

现在,有一个 Intersection observer 接口可以方便我们操作,它可以异步观察目标元素与祖先元素或顶层文件的交集变化。简单的说,以前我们需要自己去写滚动监听事件函数,现在,这个 API 可以帮助我们,我们只需要统一写一个 观察函数 ,每当想观察的元素进入视口,也就是我们看见它时,就执行相应的操作。看看以下 js 代码:

1
2
3
4
5
6
7
8
9
10
11
12
<style>   // css 部分
.lazy-load {
width: 400px;
height: 300px;
}
</style>
<div>
<img class="lazy-load" data-src="https://source.unsplash.com/random/600" alt="">
<img class="lazy-load" data-src="https://source.unsplash.com/random/700" alt="">
<img class="lazy-load" data-src="https://source.unsplash.com/random/800" alt="">
<img class="lazy-load" data-src="https://source.unsplash.com/random/900" alt="">
</div>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
document.addEventListener("DOMContentLoaded", function() {
let lazyImages = [...document.querySelectorAll('.lazy-load')];
if ("IntersectionObserver" in window) {
// 创建一个观察函数,以便待会调用
let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
let lazyImage = entry.target;
lazyImage.src = lazyImage.dataset.src; // 替换 src URL
lazyImageObserver.unobserve(lazyImage); // 解除观察
}
});
});
// 对所有需要懒加载的图片进行 “暗中观察”
lazyImages.forEach(function(lazyImage) {
lazyImageObserver.observe(lazyImage);
});
}else{
alert('您的浏览器不支持 IntersectionObserver');
}
});

可以看到,里面监听了 DOMContentLoaded 事件,当初始的 HTML 文档被完全加载和解析完成之后,这个事件就被触发,在页面初始之后获取到所有图片元素,然后进行观察。

那既然这个 API 这么好,又简便易用,有没什么缺点呢?相信你看了上面的代码就能知晓,对,浏览器兼容问题!!

can I use
can I use

可以看到,还是有很多泛红,只有 Chrome 支持的最好,从 58 以上版本就完全支持了,Firefox 也不错。如果你的项目不需要考虑兼容的话,可以尝试使用下它,看看效果。

Chrome 浏览器自带

这个方法厉害了,没有前面两种方法那么复杂,它是 Chrome 自带的原生 lazyload 属性,只需要一个开关。

chrome://flags/#enable-lazy-image-loading

复制它到 Chrome 浏览器的地址栏,然后找到如下选项,将其设置为「Enabled」。


然后在 HTML 标签里开启:
1
<img src="https://source.unsplash.com/random/600" alt="" lazyload="on">

不需要多余的代码,不需要 JS ,简直强大。

比较三者

秉承着尝试捣鼓新技术的原则,应该优先使用 Intersection Observer ,随着越来越多的浏览器支持会更广泛地应用;但如果要考虑浏览器的兼容问题,那就要使用平常的 scroll,resize 事件监听了,配合 offsetTop 、innerHeight 以及 pageYOffset 几个属性实现。至于最简单粗暴的那个方法,很明显只能在特定的 Chrome 70 以上版本中使用,有很大的局限性,不过现在使用 Chrome 的人非常的多,所以也是有用处的。综合来看,应该将 Intersection Observerscroll,resize 结合起来使用,这可能是最优也最兼容的方案。

参考资料

[1] MDN 文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Intersection_Observer_API
[2] 延迟加载图像和视频:https://developers.google.com/web/fundamentals/performance/lazy-loading-guidance/images-and-video/
[3] Lazy load images:https://medium.com/@filipvitas/lazy-load-images-with-zero-javascript-2c5bcb691274