IntersectionObserver
提供了一套异步检测目标元素与祖先元素或 viewport 相交情况变化的方法。如果不兼容,需要使用 Element.getBoundingClientRect() 方法去获取相对位置,外加 scroll 事件监听去实现对应的功能。
使用场景
- 图片懒加载:当图片滚动到可见时才进行加载;
- 内容无限滚动:也就是用户滚动到接近内容底部时直接加载更多,而无需用户操作翻页,给用户一种网页可以无限滚动的错觉;
- 检测广告的曝光情况——为了计算广告收益,需要知道广告元素的曝光情况;
- 在用户看见某个区域时执行任务或播放动画;
回调函数触发的时机
- 每当目标 (target) 元素与设备视窗或者其他指定元素 发生交叉 的时候执行。设备视窗或者其他元素我们称它为根元素或根 (root);
- Observer 第一次监听目标元素的时候, 初始化也需要判断是否有交叉部分;
- 回调函数将会被当作任务(也称作 宏任务)增加到主线程的消息队列中等待执行;
配置属性
- root:指定根 (root) 元素,用于检查目标的可见性。必须是目标元素的父级元素。如果未指定或者为null,则默认为浏览器视窗;
- rootMargin: 根 (root) 元素的外边距。类似于 CSS 中的 margin 属性,比如 “10px 20px 30px 40px” (top, right, bottom, left)。如果有指定 root 参数,则 rootMargin 也可以使用百分比来取值。该属性值是用作 root 元素和 target 发生交集时候的计算交集的区域范围,使用该属性可以控制 root 元素每一边的收缩或者扩张。默认值为 0, 如果想要 target 元素还没真正交叉的时候触发,可以通过这个值来调节,也就是说,可以设置在外边距多少范围内也可以触发;
- threshold: 可以是单一的 number 也可以是 number 数组,target 元素和 root 元素相交程度达到该值的时候 IntersectionObserver 注册的回调函数将会被执行。如果你只是想要探测当 target 元素的在 root 元素中的可见性超过 50% 的时候,你可以指定该属性值为 0.5。如果你想要 target 元素在 root 元素的可见程度每多 25% 就执行一次回调,那么你可以指定一个数组 [0, 0.25, 0.5, 0.75, 1]。默认值是 0 (意味着只要有一个 target 像素出现在 root 元素中,回调函数将会被执行)。该值为 1.0 含义是当 target 完全出现在 root 元素中时候 回调才会被执行。
停止某个观察对象或者取消所有观察
- unobserve: 方法入参对应的 target 元素可取消观察;
- disconnect:方法只要调用便会取消所有的观察;
注意事项
注册的回调函数将会在主线程中被执行,回调任务将在浏览器空闲的时候被增加在任务队列中等待执行, 如果耗时操作会使得当前任务占用时间过长,造成卡顿。所以该函数执行速度要尽可能的快。如果有一些耗时的操作需要执行,建议使用 Window.requestIdleCallback() 方法。
代码示例
<body>
<div style="height: 300px;background-color: #f7f7f7;" class="div-1">1</div>
<div style="height: 300px;" class="div-2">2</div>
<div style="height: 300px;" class="div-3">3</div>
<div style="height: 300px;background-color: #f7f7f7;" class="div-4">4</div>
</body>
<script>
function intersectionExecFunc(infos, intersectionOpts) {
infos.forEach(info => {
/**
* Each info describes an intersection change for one observed target element:
* info 描述每一个被观察元素的交叉信息的改变
*/
/** 绑定元素矩形区域信息, 包括 x, y, width, height, top, left, right, bottom */
console.log('boundingClientRect', info.boundingClientRect);
/** 交叉比例, 也可以理解为与父级的交叉部分比例 */
console.log('intersectionRatio', info.intersectionRatio);
/** 交叉部分区域信息 */
console.log('intersectionRect', info.intersectionRect);
/** 是否满足 threshold 设定的交叉比例 */
console.log('isIntersecting', info.isIntersecting);
/** 对应的设置的父元素的矩形区域信息 */
console.log('rootBounds', info.rootBounds);
// console.log(info.target);
/** 交叉的时间, 对比时间为实例化的时间 */
console.log('time', info.time);
console.log('~~~~~~~~~~~~');
});
};
const intersectionOption = {
// root: document.querySelector('.div-4'),
/** 触发方法的临界值, 当达到这个交叉比例时触发, 如果是一个数组, 那么在对应的临界点便触发, 取值在 0.0 和 1.0 之间 */
threshold: 0.5,
};
const intersection = new IntersectionObserver(intersectionExecFunc, intersectionOption);
/** 创建的交叉观察对象可观察多个是否可见的元素 */
intersection.observe(document.querySelector('.div-3'));
intersection.observe(document.querySelector('.div-4'));
/** 可解除对应元素的观察 */
// intersection.unobserve(document.querySelector('.div-3'));
/** 停止所有的观察 */
// intersection.disconnect();
</script>
如上所示,创建的观察实例可以观察多个元素。
ResizeObserver
提供了一种高性能的机制,通过该机制,代码可以监视元素的大小更改,并且每次大小更改时都会向观察者传递通知。比如 Element 内容盒或边框盒及 SVGElement 边界尺寸的变化。注意, 初始化的时候会触发一次。
使用介绍
如下简要使用示例, 实际使用中, 如果监听了某个元素的变动, 一定要调用 unobserve 取消某个元素的监听 或者 调用 disconnect() 取消所有元素的监听, 以免造成性能问题
const resizeObserver = new ResizeObserver((entries, resizeOb) => {
/* 如果监听多个这里就是多项 **/
for (const entry of entries) {
console.log(entry);
/* Firefox 实现的 contentBoxSize 是一个单项值, 并非数组 **/
const contentBoxSize = Array.isArray(entry.contentBoxSize)
? entry.contentBoxSize[0]
: entry.contentBoxSize;
console.log(contentBoxSize);
}
/* 在内部也能直接取到实例, 对其进行操作, 比如取消监听 **/
console.log('对应的实例 =>', resizeOb);
});
/* 假设页面中有某个元素 **/
const divEl = document.querySelector('.container');
/* 监听页面中某个元素 **/
resizeObserver.observe(divEl);
/* 取消某个监听 **/
// resizeObserver.unobserve(divEl);
/* 取消全部监听 **/
// resizeObserver.disconnect();
observe 方法支持两个参数:
observe(target, options);
- target: 监听的目标
- options: 目前支持的属性设置
- box: 可选值有 ‘content-box’ (默认值), ‘border-box’, ‘device-pixel-content-box’;
该种监听方式产生的应该是一个宏任务, 具体可点击查看该实例, https://www.nqone.com/router/do…。通过 performance录制任务执行过程。
MutationObserver
该接口可以监控 DOM 树所做的修改,是旧的 Mutation Events 功能的替代品,该功能是 DOM3 Events 规范的一部分。
简单使用
如下, 监听页面中某个元素, 定义一个按钮点击修改内容和属性, 可以看到打印两个结果, 知道具体修改了哪些内容, 如果不使用了一定要调用 disconnect 方法取消监听。
/* 假设页面中有该元素 **/
const el = document.querySelector('.container');
function mutationObFunc(mutations, paramOb) {
/*
* 每一项都是检测到的变动项结果, 可获取相关参数
* 如果修改多个或者监听多个不同的修改, 就会产生多项
*/
for (const mutation of mutations) {
console.log(mutation);
}
/* 通过该实例可以取消监听, 做些操作 **/
console.log('实例对象 =>', paramOb);
}
const mutationOb = new MutationObserver(mutationObFunc);
const observerOptions = {
/* 观察目标子节点的变化,是否有添加或者删除 **/
childList: true,
/* 观察属性变动 **/
attributes: true,
/* 观察后代节点,默认为 false **/
subtree: true,
};
mutationOb.observe(el, observerOptions);
/* 如果不需要了, 停止观察 **/
// mutationOb.disconnect();
/* 点击按钮修改属性和内容 **/
const btn = document.querySelector('.btn');
btn.addEventListener('click', () => {
el.innerText = 'q w e r t y';
el.style.width = '100px';
});
常用方法:
- observe: 需要观察的元素, 支持两个参数, 第一个参数对应的元素, 第二个参数为一些配置项;
- takeRecords: 获取已检测到但尚未由观察者的回调函数处理的所有匹配 DOM 更改的列表,使变更队列保持为空;
- disconnect: 停止观察变动;
该方式产生的任务应该是一个微任务, 具体示例可点击查看 https://www.nqone.com/…, 通过performance可录制执行过程。
参考文档:
IntersectionObserver观察可视区, https://www.nqone.com…
ResizeObserver观察元素尺寸, https://www.nqone.com…
MutationObserver观察DOM变动, https://www.nqone.com…