如何实现图片懒加载

529 阅读3分钟

背景

图片懒加载是针对图片加载时机的一种优化,在一些图片量比较大的网站(比如电商网站首页,或者团购网站、小游戏首页等),如果我们尝试在用户打开页面的时候,就把所有的图片资源加载完毕,那么很可能会造成白屏、卡顿等现象,因为图片真的太多了,一口气处理这么多任务,浏览器做不到啊!

懒加载是为了让浏览器只加载可视区内的图片,可视区外的大量图片不进行加载,当页面滚动到后面去的时候再进行加载。这样做有很多好处可以增加首屏加载的速度,毕竟,用户点开页面的瞬间,呈现给他的只是首屏,我们只要把首屏的资源图片加载处理就可以了,至于下面的图片,当用户下滑当当前位置的时候,在加载出来也是没问题的,对于性能压力也小了,用户体验也没有变差。

解答

图片懒加载的原理就是需要知道图片是否在可视区内了,当图片到达可视区内就需要去请求对应的图片加载出来

页面中的img标签一般如下写

<img class="lazyload" src="placeholder.jpg" data-src="real_image.jpg" />

其中src首先赋值一个占位的图片,一般是一个很小的图片,进行占位,data-src是实际需要展示的图片,原理就是当图片在可视区内的时候将data-src的图片选渲染出来即可。

1、原生实现

Chrome 76 将原生支持图片的惰性加载,支持对img和iframe进行延迟加载,只需要将loading属性设置为lazy即可。

<img src="celebration.jpg" loading="lazy" alt="..." />
<iframe src="video-player.html" loading="lazy"></iframe>

原生实现的好处是,不需要任何脚本,纯原生HTML即可,简单方便,支持多种属性

  • lazy:对资源进行延迟加载。
  • eager:立即加载资源。
  • auto:浏览器自行判断决定是否延迟加载资源。

原生的坏处就是在于浏览器的支持率不是很高,将来肯定是非常好的。

我们知道由于浏览器的支持率不是很好,上面的方案固然很好,但是使用的并不是很多,所以下面介绍几种更加常见的懒加载方案。

2、Element.getBoundingClientRect()

getBoundingClientRect返回值是一个 DOMRect 对象,这个对象是由该元素的 getClientRects() 方法返回的一组矩形的集合, 即:是与该元素相关的CSS 边框集合 。DOMRect 对象包含了一组用于描述边框的只读属性——left、top、right和bottom,单位为像素。除了 width 和 height 外的属性都是相对于视口的左上角位置而言的。

有了这个API后我们很同意获取图片的top值,当top值小于可视区的高度的时候就可以任何图片进入了可视区,直接加载图片即可

element.getBoundingClientRect().top < document.documentElement.clientHeight

由于需要在滚动的时候去监听图片的位置,所以我们需要使用到window.onscroll事件,我们在事件内部处理相关的逻辑即可。

3、通过相对计算获取元素位置

  1. 通过document.documentElement.clientHeight获取屏幕可视窗口高度。
  2. 通过element.offsetTop获取元素相对于文档顶部的距离。
  3. 通过document.documentElement.scrollTop获取浏览器窗口顶部与文档顶部之间的距离,也就是滚动条滚动的距离。 然后判断2-3<1是否成立,如果成立,元素就在可视区域内。
element.offsetTop -  document.documentElement.scrollTop < document.documentElement.clientHeight

此方法也需要在滚动的时候去监听图片的位置,所以我们需要使用到window.onscroll事件,我们在事件内部处理相关的逻辑即可。

4、使用IntersectionObserver

const observer = new IntersectionObserver(callback, observerConfig)
const imgList = document.querySelectorAll(".lazyload");
const observer = new IntersectionObserver(entries => {
    entries.forEach(item => {
        if (item.isIntersecting) {
            item.target.src = item.target.dataset.origin; // 判断在可视区了,把data-origin的值放到src
            observer.unobserve(item.target); // 已经加载过的图片停止进行监听
        }
    });
});
imgList.forEach(item => observer.observe(item));

补充

1、优化

由于上面某些情况是需要使用到window.scroll事件的,所以我们可以增加节流来减少事件处理函数的调用次数。 假设我们判断是否可视区逻辑为函数loadImage那么我们可以如下处理。

window.onscroll = throttle(loadImage, 500)

2、拓展

上面后续三种方法不仅仅可以使用在图片的懒加载上面,其实所有可以懒加载的地方都可以通过这种方式进行判断,比如列表分页加载,我们可以通过这种方式进行判断是否需要进行下一页的加载,比如我们需要埋曝光埋点的时候,可以通过这种方法判断元素是否曝光,进行埋点事件的触发。