2021年6月23日


IntersectionObserver

1.场景需求:

在项目当中经常会遇到列表的请求,比如当数据过多时就会造成卡顿,可以使用虚拟列表,插件有react-virtualized…,还可以使用intersectionObserver来进行优化,原理就是根据通过判断元素是否进入视口来进行加载,常见的方法有以下三种

1. el.offsetTop - document.documentElement.scrollTop <= viewPortHeight
2. el.getBoundingClientRect().top <= viewPortHeight
3. intersectionRatio > 0 && intersectionRatio <= 1

2.缺点:

  • 前面两种方式都是使用scroll的方式,会造成频繁的触发事件,容易造成性能问题,所以还需要做防抖的处理
  • 另外获取srollTop的值和getBoundingClientRect方法都会导致回流
  • 滚动事件会绑定多个事件处理函数,阻塞UI渲染

3.IntersectionObserver

IntersectionObserver时浏览器提供的方法,兼容性好像不是特别高,可以使用ployfill,IntersectionObserver API 是异步的,不随着目标元素的滚动同步触发。

实例方法
  • observe 开始监听一个目标元素(target),target必须是root的后代
  • unobserve 停止监听一个目标元素
  • takeRecords 返回所有监听的目标元素集合
  • disconnect 停止所有监听
const io = new IntersectionObserver(callback, option);
    1. callback参数:目标元素的可见性变化时,就会调用观察器的回调函数callback。一般会触发两次一次进入视口一次离开视口
    const io = new IntersectionObserver(
      entries => {
        console.log(entries);
      }
    );

    callback函数的参数(entries)是一个数组,每个成员都是一个IntersectionObserverEntry对象。举例来说,如果同时有两个被观察的对象的可见性发生变化,entries数组就会有两个成员。

  • option参数:

    1. threshold:性决定了什么时候触发回调函数。它是一个数组,每个成员都是一个门槛值,默认为[0],即交叉比例(intersectionRatio)达到0时触发回调函数。
    2. root:顾名思义,就是监听对象的父元素
    3. rootMargin:它使用CSS的定义方法,比如10px 20px 30px 40px,表示 top、right、bottom 和 left 四个方向的值,从而影响intersectionRect交叉区域的大小

4.IntersectionObserverEntry

IntersectionObserverEntry对象提供目标元素的信息,一共有六个属性。

  • time:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒
  • target:被观察的目标元素,是一个 DOM 节点对象
  • rootBounds:根元素的矩形区域的信息,getBoundingClientRect()方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回null
  • boundingClientRect:目标元素的矩形区域的信息
  • intersectionRect:目标元素与视口(或根元素)的交叉区域的信息
  • intersectionRatio:目标元素的可见比例,即intersectionRectboundingClientRect的比例,完全可见时为1,完全不可见时小于等于0

5.代码示例

// useLoading.js
/*
 * @Description: 滚动加载,只需传入对呀ref即可
 * @Autor: wxy
 * @Date: 2021-06-23 16:43:10
 * @LastEditors: wxy
 * @LastEditTime: 2021-06-23 17:21:44
 */
import { useState, useEffect } from 'react';

const useLoading = (ref) => {
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    setTimeout(() => {
      if (!ref.current) {
        return () => { }
      }
      const node = ref.current;
      const observer = new IntersectionObserver((entries, observer) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            setLoading(false);
            observer.unobserve(entry.target);
          }
        });
      });
      if (node != null) {
        observer.observe(node);
      }

      return () => {
        observer.disconnect();
      }
    }, 1000)
  }, [ref]);
  return loading;
}

export default useLoading;

Author: wxy
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source wxy !
  TOC