图片懒加载原理

欢迎阅读,本文将介绍图片懒加载的两种方式。

前言

目前项目中需要加载的图片很多,我们知道服务器请求次数过多,势必会影响到性能。为提高网页性能,需要实现图片的“按需加载”(图片懒加载),即图片默认暂时不加载,当图片即将进入可视区域时,再进行加载。

原理

<img> 的 src 的值存在时,浏览器发送请求,若没有 src,则不发送请求。所以我们在使用 <img> 标签时,可以先在 src 里放置一张默认 loading 图,真实的图片地址以数组的形式存放在其 data-src 属性中,如:

1
<img src ="loading.gif" data-src="img.jpg">

当图片进入可视区域时,用 data-src 的值替换 src 里的默认值,从而实现图片的按需加载

实现方法

实现的重点是怎么判断图片是否在可视区域。

方法一:

使用 IntersectionObserver API,可以自动“观察”元素是否在可视区域,但此方法兼容性较差,目前 Chrome 只兼容到 Chrome 51+

方法二:

监听 scroll 事件,调用目标元素的 getBoundingClientRect() 方法,得到该元素对应于“可视区域”左上角的坐标,再判断其是否在“可视区域”内,但操控滚动条时,会频繁操作DOM,容易造成性能问题,需要进行函数节流(请参考:http://www.ruanyifeng.com/blog/2015/09/web-page-performance-in-depth.html)。

实现

方法一:

浏览器提供的原生构造函数 IntersectionObserver 有两个参数:callback 回调函数,option 配置项(可选)

1
var io = new IntersectionObserver(callback, option);

开始观察:io.observe(document.getElementById('example'));

停止观察:io.unobserve(element);

关闭观察器:io.disconnect();

这里需要注意:io.observe() 每次观察一个DOM节点对象,如果需要观察多个,需要调用多次。

callback参数

目标元素可见性发生变化时,会调用 callback 函数,callback 函数的参数(entries)是一个数组,每个成员都是一个IntersectionObserverEntry 对象,该对象提供目标元素的信息,一共有六个属性:

  1. time :可见性发生变化的时间,是一个高精度时间戳,单位为毫秒

  2. target :被观察的目标元素,是一个 DOM 节点对象

  3. rootBounds :根元素的矩形区域的信息,getBoundingClientRect() 方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回null

  4. boundingClientRect :目标元素的矩形区域的信息

  5. intersectionRect :目标元素与视口(或根元素)的交叉区域的信息

  6. intersectionRatio :目标元素的可见比例,即 intersectionRectboundingClientRect 的比例,完全可见时为 1,完全不可见时小于等于 0。

1
2
3
4
5
6
7
8
const callbackFun = (entries) => {
entries.forEach((item) => {
if (item.intersectionRatio <= 0) return;
const { target } = item;
// 将 h5 自定义属性赋值给 src (进入可见区则加载图片)
target.src = target.dataset.src;
})
}

option参数

参数 option 可设置 threshold 属性、root 属性,rootMargin 属性。

其中 threshold 属性决定了什么时候触发回调函数。它是一个数组,默认为 [0]
threshold = [0, 0.25] 表示当目标元素与可视区域的交叉区域分别达到 0%、25% 时触发回调函数。
另外 rootrootMargin 属性,root 属性指定目标元素所在的容器节点(即根元素),rootMargin 属性用来定义根元素的 margin,用来扩展或缩小 rootBounds 这个矩形的大小,从而影响 intersectionRect 交叉区域的大小。适合目标元素在容器内滚动的场景。

方法二:

方法 Element.getBoundingClientRect() 返回元素的大小及其相对于视口的位置,这个方法返回一个名为 ClientRect的DOMRect 对象,包含了 width、height、top、bottom、left、right 的值。
我们知道 window.innerHeight 可以获得可视区域的高度,getBoundingClientRect().top 可以获取图片到可视区域顶部的距离,因此当:getBoundingClientRect().top <= window.innerHeight 时,图片在可视区域内。

判断图片是否进入可视区域

1
2
3
4
5
eleIsInView = (element) => {
const eleRect = element.getBoundingClientRect();
viewHeight = window.innerHeight;
return eleRect.top <= viewHeight + 50; // 提前50px的距离加载图片
};

加载图片

页面加载时需要检查图片是否在可视区域,如果在就加载。

1
2
3
4
window.addEventListener('scroll', () => {
if (!eleIsInView(element)) return false;
element.setAttribute('src', element.getAttribute('data-src'));
}, false);