图片延迟加载的2种实现方案

677 阅读4分钟

图片懒加载的意义

虽然当代浏览器在渲染 DOM 树的时候。遇到 img 并不会阻碍 DOM 树的渲染(浏览器会开辟HTTP线程请求图片资源文件),但是在生成 RENDER TREE 后,浏览器进行渲染的时候,会把渲染树和图片一起进行绘制,这时就会遇到一些影响性能的(影响页面第一次加载的速度)问题:

  1. 如果请求的图片资源过多,我们同时可以开辟的HTTP线程只有6个,这样图片资源预先的加载,会影晌其他资源的请求速度;
  2. 第一次绘制页面的时候,如果开始绘制图片,也需要消耗很多的时间,这样也影晌页面第一次打开的速度。

所以对于这种情况:

  1. 我们一般都采用图片的懒加载(开始需要展示图片的位置,我们基于默认图或者一个空白的盒子占位,真实图片不加载,只有当页面的一次渲染完以及滚动到当前所在的区域时候,冉去加载真实的图片);
  2. 我们可以把图片 base64(虽然也会慢一些,但是总比不做强)。

getBoundingClientRect

getBoundingClientRect 用于获取某个元素相对于视窗的位置集合。集合中有top, right, bottom, left等属性。

clientRect = lazyImageBox.getBoundingClientRect();
  1. clientRect.top:元素上边到视窗上边的距离;
  2. clientRect.right:元素右边到视窗左边的距离;
  3. clientRect.bottom:元素下边到视窗上边的距离;
  4. clientRect.left:元素左边到视窗左边的距离。

盒子底边距离视口上面的距离 bottom 小于等于视口的高度,再开始加载图片。

let lazyImageBox = document.querySelector('.lazyImageBox'),
    lazyImage = lazyImageBox.querySelector('img');

const singleLazy = function singleLazy() {
    let trueImg = lazyImage.getAttribute('lazy-image');
    lazyImage.src = trueImg;
    lazyImage.onload = () => {
        // 真实图片加载成功
        lazyImage.style.opacity = 1;
    };
    lazyImageBox.isLoad = true;
};

const lazyFunc = function lazyFunc() {
    // 防止重复处理
    if (lazyImageBox.isLoad) return;
    let A = lazyImageBox.getBoundingClientRect().bottom,
        B = document.documentElement.clientHeight || document.body.clientHeight;
    if (A <= B) {
        singleLazy();
    }
};

// window.onscroll = lazyFunc; //默认浏览器会在最快的反应时间内,监听到scroll事件的触发,从而执行lazyFunc这个方法,这样导致触发频率太高了 -> 节流处理
window.onscroll = throttle(lazyFunc);

IntersectionObserver

IntersectionObserver 可以自动"观察"元素是否可见。本质是目标元素与视口产生一个交叉区,所以这个 API 叫做"交叉观察器"。

它的用法非常简单。

let ob = new IntersectionObserver(callback, option);

上面代码中,IntersectionObserver是浏览器原生提供的构造函数,接受两个参数:callback是可见性变化时的回调函数,option是配置对象(该参数可选)。

构造函数的返回值是一个观察器实例。实例的observe方法可以指定观察哪个 DOM 节点。

// 开始观察

ob.observe(document.getElementById('example'));

// 停止观察
ob.unobserve(element);

// 关闭观察器
ob.disconnect();

上面代码中,observe的参数是一个 DOM 节点对象。如果要观察多个节点,就要多次调用这个方法。

callback 参数

目标元素的可见性变化时,就会调用观察器的回调函数callback。

callback一般会触发两次。一次是目标元素刚刚进入视口(开始可见),另一次是完全离开视口(开始不可见)

let ob = new IntersectionObserver(changes => {
    // changes 是一个数组,包含所有监听的DOM元素和视口的交叉信息
    let item = changes[0],
        {
            isIntersecting,
            target
        } = item;
});

callback函数的参数(changes)是一个数组,每个成员都是一个IntersectionObserverEntry对象。举例来说,如果同时有两个被观察的对象的可见性发生变化,changes 数组就会有两个成员。

IntersectionObserverEntry 对象

IntersectionObserverEntry提供观察元素的信息,有八个属性。

1. boundingClientRect:目标元素的矩形信息;
2. intersectionRatio:相交区域和目标元素的比例值 ;
3. intersectionRect:目标元素和视窗(根)相交的矩形信息 可以称为相交区域;
4. isIntersecting:目标元素当前是否可见 Boolean值 可见为true;
5. rootBounds:根元素的矩形信息,没有指定根元素就是当前视窗的矩形信息;
6. target:观察的目标元素;
7. time:返回一个记录从IntersectionObserver的时间到交叉被触发的时间的时间戳。

intersectionRect/boundingClientRect:不可见时小于等于0;

Option 对象

IntersectionObserver构造函数的第二个参数是一个配置对象。它可以设置以下属性。

threshold属性决定了什么时候触发回调函数。它是一个数组,每个成员都是一个门槛值,默认为[0],即交叉比例(intersectionRatio)达到0时触发回调函数。

用户可以自定义这个数组。比如,[0, 0.25, 0.5, 0.75, 1]就表示当目标元素 0%、25%、50%、75%、100% 可见时,会触发回调函数。

let lazyImageBox = document.querySelector('.lazyImageBox'),
    lazyImage = lazyImageBox.querySelector('img');
const singleLazy = function singleLazy() {
    let trueImg = lazyImage.getAttribute('lazy-image');
    lazyImage.src = trueImg;
    lazyImage.onload = () => {
        lazyImage.style.opacity = 1;
    };
};
// 使用DOM监听器 IntersectionObserver:监听一个或者多个DOM元素和可视窗口的交叉信息
let ob = new IntersectionObserver(changes => {
    // changes是一个数组,包含所有监听的DOM元素和视口的交叉信息
    let item = changes[0],
        {
            isIntersecting,
            target
        } = item;
    if (isIntersecting) {
        // 完全出现在视口中了
        singleLazy();
        ob.unobserve(lazyImageBox); //加载真实图片后,移除对盒子的监听
    }
}, {
    threshold: [1]
});
ob.observe(lazyImageBox);

总结

IntersectionObserver 是新的浏览器 API , 可以更方便的实现图片延迟加载。但是 getBoundingClientRect 比起 IntersectionObserver 有更好的兼容性,因为IE无法兼容,所以要在IE实现这些功能,还是得老老实实使用基于 scroll 事件使用 getBoundingClientRect 来整。