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

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

从需求出发:

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

实现方案:

1、默认不加载图片,只加载占位符2、组件滚动条变化3、计算可视区域,触发条件4、标签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" 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来访问。

脚本代码:

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时,图片是在可视区域内的。

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'));

小结:

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

本文分享自微信公众号 - 前端自习课(FE-study)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-08-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏HACK学习

XSS绕过实战练习

写这篇博文起源来自于一次网络安全实验课,在实验虚拟环境里有一个xss挑战,估计是搬别人的xss挑战进来,我觉得挺有意思,就记录一下。有些关卡不能再虚拟环境实践,...

31410
来自专栏HACK学习

XSS绕过姿势

XSS为跨站攻击脚本,指攻击者将js脚本(可能是其他脚本)插入到web页面,当用户浏览该网页是,代码就会执行,造成恶意攻击。

23220
来自专栏公众号:googpy

带你认识优秀的python代码

有一串长的字符串names = "LI XIA , ZHAO MING ,LAO WANG *,DA XIONG >,LI MEI MEI, CHANG JIA...

12830
来自专栏finleyMa

Docker 学习系列21 远程连接Docker

Docker为C/S架构,服务端为docker daemon (daemon是守护进程的意思,进程名叫dockerd),客户端为docker.service。 ...

19210
来自专栏我的前端路

最新15个加速 Web 开发的框架和工具

我们为开发人员挑选了15个最新的  Web 开发框架,你肯定尝试一下这些新鲜的框架,有的可能略微复杂,有的提供了很多的配置选项,也有一些窗口小部件和界面交互的选...

11900
来自专栏程序员成长指北

你觉得Node.js是单线程这个结论对吗?

一提到 Node.js ,我想大家都会想到它的一个特点,单线程。但是 Node.js 在运行的时候依赖 V8 这个宿主环境,难道在宿主环境中也是单线程吗?请看正...

12020
来自专栏grain先森

前端技能自检

前端开发是一个非常特殊的行业,它的历史实际上不是很长,但是知识之繁杂,技术迭代速度之快,是其他技术所不能比拟的。

26720
来自专栏中间件兴趣圈

源码分析RocketMQ消息轨迹

从上述代码可以看出其关键点是在创建DefaultMQProducer时指定开启消息轨迹跟踪。我们不妨浏览一下DefaultMQProducer与启用消息轨迹相关...

24240
来自专栏HACK学习

【思路/技术】某大佬的BypassWAF新思路(附脚本)

网上关于安全狗的sql绕过研究,大多数是fuzz绕过的帖子,fuzz方法常常使用注释绕过,涉及到数据库特性,而且广泛用于注释语法的星号(*)可能会被网站自带的防...

25920
来自专栏Web行业观察

前端社区的恶趣味之Vanilla JS

刚刚下载了一个使用原生web组件的codepen代码的时候发现了一个“似曾相识”的名词:vanilla JS。

20410

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励