前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【JS】322- 手把手教你实现前端惰性加载

【JS】322- 手把手教你实现前端惰性加载

作者头像
pingan8787
发布2019-08-20 12:04:10
9410
发布2019-08-20 12:04:10
举报
文章被收录于专栏:前端自习课前端自习课

本文由 chunpengliu 首发于腾讯内部KM论坛,由 IMWeb 社区授权转载。点击阅读原文查看 IMWeb 社区更多精彩文章。

从需求出发:

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

实现方案:

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

知识点:

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

代码实现:

页面结构

代码语言:javascript
复制
<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" src="./img/img1.png" src="image-placeholder-logo.svg">
    </div>
    <div class="img-area">
        <img class="pic" alt="loading" src="./img/img2.png" src="image-placeholder-logo.svg">
    </div>
    <div class="img-area">
        <img class="pic" alt="loading" src="./img/img3.png" src="image-placeholder-logo.svg">
    </div>
    <div class="img-area">
        <img class="pic" alt="loading" src="./img/img4.png" src="image-placeholder-logo.svg">
    </div>
    <div class="img-area">
        <img class="pic" alt="loading" src="./img/img5.png" src="image-placeholder-logo.svg">
    </div>
</div>

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

脚本代码:

代码语言:javascript
复制
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 边框集合 。包含边框的只读属性 lefttoprightbottom,单位为像素。除了 widthheight外的属性都是相对于视口的左上角位置而言的。

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

代码语言:javascript
复制
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:

代码语言:javascript
复制
var observe = new IntersectionObserver(callback, option);

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

代码语言:javascript
复制
// 开始观察
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可以很简单的应用。下面是实现方法:

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

小结:

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-08-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端自习课 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 从需求出发:
  • 实现方案:
  • 知识点:
  • 代码实现:
  • 另一种计算方法:
  • 进一步考虑:
  • 交叉观察器:
  • 实现下拉无限滚动:
  • 小结:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档