我们在网页开发中,经常需要监听某个元素是否进入了可视区域内,从而进行相关操作,例如懒加载等;之前的做法大多都是通过监听 scroll 事件,通过获取目标元素的当前位置与视窗位置进行判断,通过这种方法需要监听 scroll 事件并且同时需要获取元素当前位置,会进行大量计算重绘等操作,可能会使页面卡顿,降低用户体验。
IntersectionObserver
接口,可以代替我们手动监听元素,可以自动“观察”元素是否可见。
概念(引自 MDN)
IntersectionObserver
接口 (从属于Intersection Observer API) 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗 (viewport) 交叉状态的方法。祖先元素与视窗 (viewport) 被称为根 (root)。
当一个IntersectionObserver
对象被创建时,其被配置为监听根中一段给定比例的可见区域。一旦 IntersectionObserver 被创建,则无法更改其配置,所以一个给定的观察者对象只能用来监听可见区域的特定变化值;然而,你可以在同一个观察者对象中配置监听多个目标元素。
const observer = new IntersectionObserver(callback: IntersectionObserverCallback, options?: IntersectionObserverInit): IntersectionObserver;
observer.observe(target);
// 最简单的一个使用示例,获取要监听的元素,新建 IntersectionObserver 实例,观察要监听元素,一个根元素可以同时监听多个元素
const divDom = document.getElementsByClassName('hidden');
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
// 过滤掉未出现在视窗中的元素
if (entry.intersectionRatio <= 0) {
return;
}
console.log(entry);
},
{ threshold: [0.2, 0.25, 0.5, 1] },);
}
);
observer.observe(divDom[0]);
observer.observe(divDom[1]);
参数
new IntersectionObserver(callback: IntersectionObserverCallback, options?: IntersectionObserverInit): IntersectionObserver)
-
callback
当元素可见比例超过指定阈值后,会调用传入的回调函数,回调函数有两个参数
此回调函数会在主线程中运行,所以此函数的执行应该尽量快,不然会导致卡顿等性能问题,可以使用异步方法或者
requestIdleCallback
,requestAnimationFrame
等方法进行优化-
entries
一个IntersectionObserverEntry对象的数组,每个被触发的阈值,都或多或少与指定阈值有偏差。
IntersectionObserverEntry
time
:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒target
:被观察的目标元素,是一个 DOM 节点对象rootBounds
:容器元素的矩形区域的信息,getBoundingClientRect()
方法的返回值,如果没有容器元素(即直接相对于视口滚动),则返回null
boundingClientRect
:目标元素的矩形区域的信息intersectionRect
:目标元素与视口(或容器元素)的交叉区域的信息intersectionRatio
:目标元素的可见比例,即intersectionRect
占boundingClientRect
的比例,完全可见时为1
,完全不可见时小于等于0
-
observer
被调用的IntersectionObserver实例。
-
-
options
可选root
必须是监听目标对象祖先元素,若不指定则默认根元素。rootMargin
一个在计算交叉值时添加至根的边界盒 (bounding_box (en-US)) 中的一组偏移量,类型为字符串 (string) ,可以有效的缩小或扩大根的判定范围从而满足计算需要。语法大致和 CSS 中的margin
属性等同; 可以参考 The root element and root margin in Intersection Observer API来深入了解 margin 的工作原理及其语法。默认值是"0px 0px 0px 0px"。threshold
规定了一个监听目标与边界盒交叉区域的比例值,可以是一个具体的数值或是一组 0.0 到 1.0 之间的数组。若指定值为 0.0,则意味着监听元素即使与根有 1 像素交叉,此元素也会被视为可见。若指定值为 1.0,则意味着整个元素都在可见范围内时才算可见。可以参考Thresholds in Intersection Observer API 来深入了解阈值是如何使用的。阈值的默认值为 0.0。
返回值
一个可以使用规定阈值监听目标元素可见部分与root
交叉状况的新的IntersectionObserver
实例。调用自身的observe()
方法开始使用规定的阈值监听指定目标,例如observer.observe(divDom[0]);
使用
IntersectionObserver
还是有很大的用途,可以取代通过onscroll
事件来监听滚动高度与目标元素之间的交互,不在完全依赖onscroll
,可以提高我们页面的性能。
-
无限滚动 通过监听列表最下方的元素,当元素出现之后,获取下一页数据,监听数据的变化,重新监听列表页最下方的元素。
const observerRef = useRef<IntersectionObserver | null>(); useEffect(() => { // 监听滚动事件,当页面滚动到底部的时候要获取下一页数据 if (canUseIntersectionObserver()) { observerRef.current = new IntersectionObserver((entries) => { if (entries[0].intersectionRatio <= 0) { return; } // 获取下一页数据,当前的元素已经触发过获取新数据事件,需要当前监听的元素停止监听 fetchNextFn(); observerRef.current!.unobserve(entries[0].target); }); } return () => { // 离开时清除状态 observerRef.current = null; }; }, []); // 监听滚动最下方的时候的事件 // 上面 fetchNextFn 触发之后,获取到的数据发生变化,要重新监听新列表最下方的元素 useEffect(() => { if (observerRef.current) { observerRef.current!.observe(observerElement as Element); } // 监听变化的列表数据 }, [observerValue]);
-
懒加载 可以代替之前使用
scroll
事件来实现懒加载,通过IntersectionObserver
来实现有更高的性能,当元素出现在视窗中即可触发图像加载<img src="placeholder.png" data-src="img-1.jpg"> <img src="placeholder.png" data-src="img-2.jpg"> <img src="placeholder.png" data-src="img-3.jpg"> const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if(entry.intersectionRatio <= 0) { return; } // 懒加载图片,完成后从监听列表中删除 entry.target.src = entry.target.dataset.src; observer.unobserve(entry.target); }); }); // 监听所需要懒加载的img 标签 Array.prototype.slice.call(document.getElementsByTagName('img')).forEach(img => { observer.observe(img); });
-
依据滚动高度修改样式
function getThresholdList(steps?: number) { const thresholds = [0]; const numSteps = steps || 20; for (let i = 1.0; i <= numSteps; i++) { let ratio = i / numSteps; thresholds.push(ratio); } return thresholds; } const headerDom = document.getElementsByClassName('header'); const observer = new IntersectionObserver( (entries) => { if (entries[0].intersectionRatio <= 0) { return; } // 在此处修改样式 modifyStyle(entries[0]) }, { threshold: buildThresholdList() }, ); observer.observe(headerDom[0]);
-
不止于此,自动播放暂停等等,都可以尝试使用。
实际使用
此 api 较新,但是在 2022.6.15 IE 正式不维护之后,其他浏览器可以尝试使用,微信小程序中也有支持此 api,当浏览器不支持此 api 时,我们也可以降级使用 onscroll
事件,可以使用下面的函数来判断浏览器是否支持,支持的话,我们就可以愉快滴使用了。
const canUseIntersectionObserver = () =>
'IntersectionObserver' in window &&
'IntersectionObserverEntry' in window &&
'intersectionRatio' in window.IntersectionObserverEntry.prototype;