前端性能优化--惰性加载

图片描述

从需求出发:

在实际的项目开发中,我遇到了一个这样的需求:一个页面模块有很多列表数据展示,每条数据都带有图片,而首次展示的图片只需要不到10张,那么我们还要一次性把所有图片都加载出来吗?显然这是不对的,不仅影响页面渲染速度,还浪费带宽(因为需要对列表进行拖动排序,需加载出全部列表,不能做分页)。我们可以在浏览器滚动到一定的位置的时候进行下载,这也就是们通常所说的惰性加载,技术上现实其中要用的技术就是图片懒加载--到可视区域再加载。
图片描述

实现方案:

1、默认不加载图片,只加载占位符
2、组件滚动条变化
3、计算可视区域,触发条件
4、<img>标签src属性加载资源

知识点:

scrollTop:外框元素的滚动高度
offsetTop:元素相对于最近的包含该元素的定位元素(具有position属性且不是static)边框的距离。如果没有定位的元素,则默认body。
offsetHeight:它返回该元素的像素高度,高度包含该元素的垂直内边距和边框,且是一个整数。
计算:可视区域的高度(offsetHeight) + 滚动条卷去的高度(scrollTop) >=  元素相对于外框的距离(offsetTop) - 偏移量 (提前加载)
图片描述

代码实现:

页面结构

<style type="text/css">
.container{
    width:200px;
    height:200px;
    position:relative;
    overflow-y:scroll;
}
.img-area{
    width:100px;
    height:100px;
}
</style>
<div class="container">
    <div class="img-area">
        <img class="pic" alt="loading" data-src="./img/img1.png" src="image-placeholder-logo.svg">
    </div>
    <div class="img-area">
        <img class="pic" alt="loading" data-src="./img/img2.png" src="image-placeholder-logo.svg">
    </div>
    <div class="img-area">
        <img class="pic" alt="loading" data-src="./img/img3.png" src="image-placeholder-logo.svg">
    </div>
    <div class="img-area">
        <img class="pic" alt="loading" data-src="./img/img4.png" src="image-placeholder-logo.svg">
    </div>
    <div class="img-area">
        <img class="pic" alt="loading" data-src="./img/img5.png" src="image-placeholder-logo.svg">
    </div>
</div>

src属性统一用一个占位图片,alt属性是在图像无法显示时的替代文本。 data-src是自定义属性,用来保存实际的图片地址,可以通过HTMLElement.dataset来访问。
脚本代码:

    var container = document.querySelector('.container');
    container.onscroll = function(){
        checkImgs();
    }
    function isInSight(el) {
        var sTop = container.scrollTop;
        var oHeight = container.offsetHeight;
        var oTop = el.offsetTop;
        return sTop + oHeight > oTop;
    }
    function checkImgs() {
      var imgs = document.querySelectorAll('.pic');
      Array.from(imgs).forEach(el => {
        if (isInSight(el)) {
          loadImg(el);
        }
      })
    }
    function loadImg(el) {
        var source = el.dataset.src;
        el.src = source;
    }
        checkImgs();

可以看出,页面加载时候,绑定外框的scroll事件,随着用户向下滚动鼠标,把img的src赋予新的值,网络重新发起请求,拉取图片。这里应该是有一些可以优化的地方,比如
1、可以只监听向下滚动时候的事件,并设置延时(使用截流函数),防制多次调用回调函数。
2、可以设一个标识符标识已经加载图片的index,当滚动条滚动时就不需要遍历所有的图片,只需要遍历未加载的图片即可。
3、可以在计算的时候,增加偏移数据,提前加载图片,并使用淡入效果,提高流畅性。

另一种计算方法:

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

这种条件下,假设bound = el.getBoundingClientRect(),随着滚动条的向下滚动,bound.top会越来越小,也就是图片到可视区域顶部的距离越来越小,当bound.top===clientHeight时,图片的上沿应该是位于可视区域下沿的位置的临界点,再滚动一点点,图片就会进入可视区域。
也就是说,在bound.top<=clientHeight时,图片是在可视区域内的。

function isInSight(el) {
    var bound = el.getBoundingClientRect();
    var clientHeight = window.innerHeight;
    return bound.top <= clientHeight;
}

 进一步考虑:

以上监听scroll,并计算元素位置来实现惰性加载。当数据达到一定量的时候,事件绑定和循环位置计算会消耗大量的性能,每次调用 getBoundingClientRect() 都会强制浏览器 重新计算整个页面的布局 ,可能给你的网站造成相当大的闪烁。这种方式有些美中不足。

交叉观察器:

IntersectionObserver 就是为此而生的,它是HTML5新增的api,可以检测一个元素是否可见,IntersectionObserver 能让你知道一个被观测的元素什么时候进入或离开浏览器的视口。
图片描述

它兼容性有限,
Chrome 51+(发布于 2016-05-25)
Android 5+ (Chrome 56 发布于 2017-02-06)
Edge 15 (2017-04-11)
iOS 不支持

不过不用担心,WICG 提供了一个polyfill,可以兼容到以下版本:
图片描述

它的用法也很简单,类似于rxjs中的observe。

var observe = new IntersectionObserver(callback, option);

 IntersectionObserver是浏览器原生提供的构造函数,接受两个参数:callback是可见性变化时的回调函数,option是配置对象(可选)。返回一个观测实例observe,可以指定观测哪个DOM节点。

// 开始观察
observe.observe(document.getElementById('example'));
callback = function(entries){
    entries.forEach((entry) => {
        if (entry.isIntersecting) {
        //开始进入,交叉状态,在此处理图片逻辑。
        } else {
        //已完全进入或完全离开
        }
    });
}
// 停止观察
observe.unobserve(element);

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

 entries是一个数组,每个成员都是一个IntersectionObserverEntry对象。举例来说,如果同时有两个被观察的对象的可见性发生变化,entries数组就会有两个成员。isIntersecting,返回一个布尔值, 如果目标元素与交叉区域观察者对象的根相交,则返回 true 。如果返回 true,则描述了变换到交叉时的状态;如果返回 false, 那么可以由此判断,变换是从交叉状态到非交叉状态。

IntersectionObserverEntry对象提供了很多有用的属性,比如target是被观察的目标元素,是一个 DOM 节点对象,intersectionRatio是目标元素的可见比例,即DOM节点的可见面积和总面积的比例,完全可见时为1,完全不可见时小于等于0,可以通过此属性设置图片的透明度,做成淡出的效果。

下拉无限滚动:

图片描述

在页面底部有一个loading状态标签。一旦标签可见,就表示用户到达了页面底部,从而加载新的条目放在标签的前面。这样做的好处是,比监听scroll和计算节省了很多性能消耗,现有IntersectionObserver可以很简单的应用。下面是实现方法:

var intersectionObserver = new IntersectionObserver(
    function (entries) {
    // 如果不可见,就返回
    if (entries[0].intersectionRatio <= 0) return;
    //在此加载新的数据
});
intersectionObserver.observe(document.getElementById('loading'));

 小结:

图片(不只有图片,主要是图片占用的资源最多最常见)惰性加载是一种网页优化技术。通过多种方案对比,使图片仅在浏览器当前视窗内出现时才加载该图片,达到减少首屏图片请求数,优化前端性能,提高用户体验。不管哪种方法,都有其自己的优势和劣势,掌握其中的原理,灵活应用才是最重要的。这对开发中遇到的问题及解决方法进行了总结,都是实战得来的经验,描述不清或者不对的地方,请多多指教。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值