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);
- callback参数:目标元素的可见性变化时,就会调用观察器的回调函数
callback
。一般会触发两次一次进入视口一次离开视口
const io = new IntersectionObserver( entries => { console.log(entries); } );
callback
函数的参数(entries
)是一个数组,每个成员都是一个IntersectionObserverEntry
对象。举例来说,如果同时有两个被观察的对象的可见性发生变化,entries
数组就会有两个成员。- callback参数:目标元素的可见性变化时,就会调用观察器的回调函数
option参数:
- threshold:性决定了什么时候触发回调函数。它是一个数组,每个成员都是一个门槛值,默认为
[0]
,即交叉比例(intersectionRatio
)达到0
时触发回调函数。 - root:顾名思义,就是监听对象的父元素
- rootMargin:它使用CSS的定义方法,比如
10px 20px 30px 40px
,表示 top、right、bottom 和 left 四个方向的值,从而影响intersectionRect
交叉区域的大小
- threshold:性决定了什么时候触发回调函数。它是一个数组,每个成员都是一个门槛值,默认为
4.IntersectionObserverEntry
IntersectionObserverEntry
对象提供目标元素的信息,一共有六个属性。
time
:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒target
:被观察的目标元素,是一个 DOM 节点对象rootBounds
:根元素的矩形区域的信息,getBoundingClientRect()
方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回null
boundingClientRect
:目标元素的矩形区域的信息intersectionRect
:目标元素与视口(或根元素)的交叉区域的信息intersectionRatio
:目标元素的可见比例,即intersectionRect
占boundingClientRect
的比例,完全可见时为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;