• home > webfront > SGML > html5 >

    懒加载优化:JavaScript IntersectionObserver API监听元素是否可见

    Author:zhoulujun Date:

    传统的滚动加载,通过滚动,去判断元素是否可见,在去根据需求去加载元素。但是,元素本身是一个复杂的组件,这样做就比较麻烦。况且滚动事件是不断触发,需要做一些列的性能优化处理

    前端性能优化很重要一项就是 按需加载,比如 懒加载,图片或者新数据请求,滚动到可见区域(浏览器视窗viewport内)再去加载。

    这个关键点,就是如何判断目标元素是否在视窗内。

    从实践中来看,关键点在于,首屏加载,他的等待可见时候(视窗内)才加载。

    传统的滚动加载,通过滚动,去判断元素是否可见,在去根据需求去加载元素。

    但是,元素本身是一个复杂的组件,这样做就比较麻烦。况且,滚动事件,只要滚动就会不断触发,需要做一些列的性能优化处理

    其次元素本身是否折叠等因素,再次增加系统设计的复杂性。所以,我们需要用的新设计方式去解决这个问题。

    首先,我们回顾传统的方法是如何处理。这里我们首先需要考虑的是否如何监控元素是否在进入到 视窗内。

    判断元素是否在视窗之内

    先安利下《图解js中DOM/event位置:clientX,offsetX,screenX,pageX,offsetLeft,scrollLeft

    传统方法:偏移量计算

    公式: el.offsetTop - parent.scrollTop <= parent.clientHeight

    function isInViewPortOfOne(el, parent = document.documentElement, offset = 100) {
      const  viewPortHeight = parent.clientHeight;
      const scrollTop  = parent.scrollTop;
      const offsetTop = el.offsetTop;
      const top = offsetTop - scrollTop;
      // 这里有个+100是为了提前加载+ 100
      return top <= viewPortHeight + offset;
    }

    这种是最早的算法,在ie时代,只有靠这个

    虽然我们知道 offsetTop、offsetLeft 属性会不断触发浏览器重绘 (可参看 《浏览器层面优化前端性能(2):Reader引擎线程与模块分析优化点》、《chrome对页面重绘和回流以及优化进行优化》)

    getBoundingClientRect计算

    公式: el.offsetTop - parent.scrollTop <= parent.clientHeight

    function isInViewPortOfOne(el, parent = document.documentElement, offset = 100) {
      const viewPortHeight = parent.clientHeight;
      const top = el.getBoundingClientRect() && el.getBoundingClientRect().top;
      // 这里有个+100是为了提前加载+ 100
      return top - viewPortHeight <= offset;
    }

    这种在ie8以上,绝大部分浏览器,都可以正常跑。ie8一下,应该是10年前的事情了

    调用目标元素的getBoundingClientRect()方法,得到它对应于视口左上角的坐标,再判断是否在视口之内。就可以满足我们的开发需求了。

    不管怎么样,以上两种方法都存在缺点:由于scroll事件密集发生,计算量很大,容易造成性能问题。虽然有 可以节流或者抖动(《Debounce和Throttle 的原理及实现》)来替身性能。

    而且如果组件本身复杂,比如图标组件,需要控制组件里面的内部逻辑开关。组件组成的群组问题。加上组件的折叠。就要写一系列的代码

    IntersectionObserver计算

    IntersectionObserver 是一个比较新的api,直接看文档即可:https://developers.google.com/web/updates/2016/04/intersectionobserver

    // 定义监听参数
    const options = {
      // 表示重叠面积占被观察者的比例,从 0 - 1 取值,
      // 1 表示完全被包含
      threshold: .1,
    };
    // 定义监听函数
    const callback = function (entries, observer) {
      /* entries.forEach((entry) => {
        entry.time;               // 触发的时间
        entry.rootBounds;         // 根元素的位置矩形,这种情况下为视窗位置
        entry.boundingClientRect; // 被观察者的位置举行
        entry.intersectionRect;   // 重叠区域的位置矩形
        entry.intersectionRatio;  // 重叠区域占被观察者面积的比例(被观察者不是矩形时也按照矩形计算)
        entry.target;             // 被观察者
      });*/
      if (entries[0].intersectionRatio > 0) {
        console.log('进入可视区域');
        // TODO
      } else {
        console.log('移出可视区域');
      }
    };
    // 创建一个监听者
    const observer = new IntersectionObserver(callback, options);
    // 指定监听元素
    const target = document.querySelector('.target');
    observer.observe(target);
    // 移除绑定
    // observer.unobserve(el)

    通过这个方法,可以让自己自己判断自己是否在视窗内。从而通过组件容器去渲染里面的组件。

    对于chrome浏览器,特别是toB的业务,不需要考虑ie问题的。这个方法,应该很实用

    https://caniuse.com/?search=IntersectionObserver

    目前兼容性方面,还是非常可观的。如果需要兼容不支持,就实现原来滚动监听的代码的了。

    参考文章:




    转载本站文章《懒加载优化:JavaScript IntersectionObserver API监听元素是否可见》,
    请注明出处:https://www.zhoulujun.cn/html/webfront/SGML/html5/2021_0110_8601.html