什么?你还在使用这种懒加载方式?

4,780 阅读6分钟

导读


相信很多小伙伴在工作中都会遇到图片请求数量过多,导致出现页面会出现卡顿的情况,在我们前端开发工作中,页面的渲染速度是十分重要的。
谈到这,我们都知道其中最大的一个问题就是图片加载速度会拖累整个页面体验。很多页面,内容很丰富,页面很长,图片较多,比如说各种商城页面。这些页面图片数量多,而且比较大,少说百来K,多则上兆。有的时候没有必要一次性加载完。否则常会出现加载半张图片或是残缺,网速不好的有时候还会出现图片加载不出来等问题。
那么我们可以使用现有的技术,先显示可视区域中的图片,再使剩余图片未显示的图片和滚动条事件做交互,“滑到你,你再显示出来”,这也就是我们常说的懒加载,来我们看一下关于懒加载的定义。

懒加载(Load On Demand)是一种独特而又强大的数据获取方法,它能够在用户滚动页面的时候自动获取更多的数据,而新得到的数据不会影响原有数据的显示,同时最大程度上减少服务器端的资源耗用。

关于懒加载的方式有很多种,在这里只会着重介绍其中两种,传统的懒加载方式以及交叉观察者这两种方式。

传统的懒加载方式


传统的懒加载方式其实就是通过监听滚动轴的变化,去请求出现在可视区域的图片。

原理

将页面中的img标签src指向一张小图片或者src为空,然后定义data-src(这个属性可以自定义命名,我采用data-src)属性指向真实的图片。

新建一个HTML文件


<div class="box">
    // 在这里我们使用自定义属性data-src存储真实的图片地址
    <img style="margin-top: 600px;" class="img" src=""
        data-src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/9/7/16d0a7d1e26e2947~tplv-t2oaga2asx-image.image"
        alt="">
    <img class="img" src=""
        data-src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/9/7/16d0a7d1e28ba479~tplv-t2oaga2asx-image.image"
        alt="">
    <img class="img" src=""
        data-src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/9/7/16d0a7d1e5b3c69c~tplv-t2oaga2asx-image.image"
        alt="">
    <img class="img" src=""
        data-src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/9/7/16d0a7d1e60d6bc9~tplv-t2oaga2asx-image.image"
        alt="">
    <img class="img" src=""
        data-src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/9/7/16d0a7d1e5ac147c~tplv-t2oaga2asx-image.image"
        alt="">
    <img class="img" src=""
        data-src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/9/7/16d0a7d1e5d0a8ef~tplv-t2oaga2asx-image.image"
        alt="">
    <img class="img" src=""
        data-src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/9/7/16d0a7d2033996bc~tplv-t2oaga2asx-image.image"
        alt="">
</div>


新建一个CSS文件


.box {
    width: 800px;
    height: 400px;
    border: 1px solid;
    overflow: auto;
}

.img {
    width: 100px;
    height: 100px;
    border: 1px solid;
    display: block;
    margin: 40px auto;
}

在这里我们基本可以实现一个这样的界面

实现懒加载


1.首先我们先去获取父级元素以及子元素

const box = document.querySelector('.box');
const imgs = document.querySelectorAll('.img');

2.监听父级滚动轴变化

box.addEventListener('scroll', handleListenScroll);

3.实现监听函数

function handleListenScroll() {
    //获取父级滚动轴距离顶部距离
    var scrollTop = box.scrollTop;
    
    //获取父级高度
    var divHeight = box.clientHeight;

    for (var i = 0; i < imgs.length; i++){
        if (imgs[i].offsetTop < divHeight + scrollTop) {
          imgs[i].src = imgs[i].getAttribute('data-src');
        }
    }
}

这样一个简单的懒加载就实现了,我们看下效果。

优化

如果直接将函数绑定在scroll事件上,当页面滚动时,函数会被高频触发,这非常影响浏览器的性能。

所谓节流,就是当持续触发事件时,保证一定时间段内只调用一次事件处理函数。这里也许会有小伙伴说了,防抖呢?先说一下防抖的概念吧,当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。根据我们的场景,显然在这里使用防抖是不太合适的。


box.addEventListener('scroll', throttle(handleListenScroll, 500));

//节流实现
function throttle(func, delay) {
    var previous = 0;
    return function () {
        let now = Date.now();
        let context = this;
        let args = arguments;
        if (now - previous > delay) {
            func.apply(context, args);
            previous = now;
        }
    };
}


小tip 在使用的时候我们也可以给每个图片一个默认图片,等图片出现在可视区域的时候进行一个替换。


交叉观察者


交叉观察者其实就是利用IntersectionObserver接口 (从属于Intersection Observer API) 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法。祖先元素与视窗(viewport)被称为根(root)。

用通俗的话来讲就是判断目标元素有没有和父级元素产生交叉行为,一张图说明一切,画的丑,请见谅~~

兼容性

呃呃呃, IE几乎全军覆没,不过,有官方提供过的polyfill,啥都不用怕,点这里

属性和方法

属性 介绍
root 所监听对象的具体祖先元素(element)。如果未传入值或值为null,则默认使用顶级文档的视窗。
rootMargin 计算交叉时添加到根(root)边界盒bounding box的矩形偏移量, 可以有效的缩小或扩大根的判定范围从而满足计算需要
thresholds 一个包含阈值的列表, 按升序排列, 列表中的每个阈值都是监听对象的交叉区域与边界区域的比率。

方法 介绍
disconnect() 使IntersectionObserver对象停止监听工作
observe() 使IntersectionObserver开始监听一个目标元素。
takeRecords() 返回所有观察目标的IntersectionObserverEntry对象数组。
unobserve() 使IntersectionObserver停止监听特定目标元素。

使用

1.新建一个IntersectionObserver对象。


const observer = new IntersectionObserver(entries => {
    // 发生交叉目标元素集合
    console.log(entries);
}, option);
    
在这里callback会返回给我们当前已监听并且发生了交叉的目标集合,我们看一下具体信息。
    

属性 介绍
boundingClientRect 空间信息
target 目标元素
isIntersecting 是否发生交叉

2.监听目标元素

const box = document.querySelector('.box');
const imgs = document.querySelectorAll('.img');

const observer = new IntersectionObserver(entries => {
    // 发生交叉目标元素集合
    entries.forEach(item => {
        // 判断是否发生交叉
        if (item.isIntersecting) {
            // 替换目标元素Url
            item.target.src = item.target.dataset.src;
            // 取消监听此目标元素
            observer.unobserve(item.target);
        }
    });
}, {
    root: box, // 父级元素
    rootMargin: '20px 0px 100px 0px' // 设置偏移 我们可以设置在目标元素距离底部100px的时候发送请求
});

imgs.forEach(item => {
    // 监听目标元素
    observer.observe(item);
});

实现

以上就是使用IntersectionObserver实现懒加载的过程,是不是很简单呢,在这里我们不再需要去监听滚动条的变化,只需要根据返回给我们的元素集合就可以实现。还要记住必须是子元素跟父元素发生交叉,是不可以去检查两个非父子关系的交叉的情况哦~~


综上 就是我对于懒加载一点小见解,也希望未来我们的页面更加流畅,so,先从懒加载开始吧~~。其实懒加载只是通过IntersectionObserver实现的一点小功能,IntersectionObserver还可以帮我们实现吸顶触底这些功能,感兴趣的小伙伴可以去试试哦。最后,文中如有错误,欢迎在评论区指正,如果这篇文章帮助到了你,欢迎点赞👍和关注,😀。

推荐阅读


参考