文章目录
- 1、IntersectionObserver 交叉观察器
- 用法
- 使用场景
- 2、MutationObserver 变动观察器
- 用法
- 使用场景
- 3、ResizeObserver 尺寸变化观察器
- 用法
- 使用场景
- 4、PerformanceObserver 性能观察器
- 用法
- 使用场景
- 5、ReportingObserver
- 用法
- 使用场景
- 总结
网页开发中我们经常要处理用户交互,我们会用 addEventListener 添加事件监听器来监听各种用户操作,比如 click、mousedown、mousemove、input 等,这些都是由用户直接触发的事件。
那么对于一些不是由用户直接触发的事件呢? 比如元素从不可见到可见、元素大小的改变、元素的属性和子节点的修改等,这类事件如何监听呢?
浏览器提供了 5 种 Observer 来监听这些变动:IntersectionObserver
、MutationObserver
、ResizeObserver
、PerformanceObserver
、ReportingObserver
。
1、IntersectionObserver 交叉观察器
IntersectionObserver
是一个 Web API,它可以异步地监测一个元素与其祖先或顶级文档视口之间的交叉状态。它会告诉我们目标元素是否进入或离开了视口,或者与其他元素重叠。
用法
const io = new IntersectionObserver(callback, option);
IntersectionObserver 是浏览器原生提供的构造函数,接受两个参数:
- callback:可见性发现变化时的回调函数。callback一般会触发两次。一次是目标元素刚刚进入视口,另一次是完全离开视口。
- option:配置对象(可选)
- threshold: 决定了什么时候触发回调函数。它是一个数组,每个成员都是一个门槛值,默认为[0],即交叉比例(intersectionRatio)达到0时触发回调函数。用户可以自定义这个数组。比如,[0, 0.25, 0.5, 0.75, 1]就表示当目标元素 0%、25%、50%、75%、100% 可见时,会触发回调函数。
- root: 用于观察的根元素,默认是浏览器的视口,也可以指定具体元素,指定元素的时候用于观察的元素必须是指定元素的子元素
- rootMargin: 用来扩大或者缩小视窗的的大小,使用css的定义方法,10px 10px 30px 20px表示top、right、bottom 和 left的值。可以使用像素或百分比来调整边界区域。
构造函数的返回值是一个观察器实例。实例一共有4个方法:
- observe: 开始监听特定元素
- unobserve: 停止监听特定元素
- disconnect: 关闭监听工作
- takeRecords: 返回所有观察目标的对象数组
// 获取元素
const target = document.getElementById("dom");
const options = {
root: null, // 视口的根元素,默认为整个文档视口
rootMargin: '0px',
threshold: 0.5 // 目标元素可见度的阈值,默认为 0(完全不可见)到 1(完全可见)
};
// 实例化交叉观察器
const io = new IntersectionObserver(callback, option);
// 开始观察:该方法需要接收一个target参数,值是Element类型,用来指定被监听的目标元素
io.observe(target);
// 停止观察:该方法需要接收一个target参数,值是Element类型,用来指定停止监听的目标元素
io.unobserve(target);
// 关闭观察器:该方法不需要接收参数,用来关闭观察器
io.disconnect();
// 获取被观察元素:该方法不需要接收参数,返回所有被观察的对象,返回值是一个数组
const observerList = io.takeRecords();
使用场景
实现滚动动画、懒加载、虚拟列表、加载更多、元素吸顶、吸底
2、MutationObserver 变动观察器
MutationObserver
是一个 Web API,用于异步监听DOM对象的变更(包括子节点),当节点属性发生变化,或执行增删改操作时执行对应的callback。MutationObserver为我们提供了一种十分方便的监听DOM变化的方式。
注:在Mutation标准化之前,开发者对DOM变化的非官方监听方式是使用定时器(轮询)机制,通过setTimeout或者setInterval来进行宏任务创建,观察节点的变化;
用法
const mo = new MutationObserver(target, option);
MutationObserver 是浏览器原生提供的构造函数,接受两个参数:
- target: 目标元素
- option:配置对象
- childList:默认为false,设置为true,可观察目标子节点的变化;比如添加或删除目标子节点,不包括修改子节点以及子节点后代的变化
- subtree:默认是false,设置为true后可观察后代节点
- attributes:默认为false,设置为true,可观察目标属性的改变
- attributeFilter:特性名称数组,只观察选定的特性
- characterData:是否观察文本内容
- attributeOldValue:是否将特性的旧值和新值都传递给回调,如果设置为true或省略,则相当于设置为true,表示需要记录改变前的目标属性值,设置了attributeOldValue可以不用设置attribute
- characterDataOldValue:是否将 node.data 的旧值和新值都传递给回调,如果设置为true或省略,则相当于设置为true,表示需要记录改变之前的目标数据,设置了characterDataOldValue可以不用设置characterData
// 目标元素
const targetElement = document.querySelector('#target');
// 创建一个MutationObserver实例
const mo= new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((addedNode) => {
console.log(`添加了子元素:${addedNode.nodeName}`);
// 执行相应的处理逻辑
});
mutation.removedNodes.forEach((removedNode) => {
console.log(`移除了子元素:${removedNode.nodeName}`);
// 执行相应的处理逻辑
});
}
});
});
// 配置观察器
const config = {
childList: true, // 观察直接子节点
subtree: true, // 及其更低的后代节点
};
// 启动观察器
mo.observe(targetElement, config);
使用场景
监测元素的添加或移除、动态内容加载、表单验证、响应式布局、自定义组件开发
3、ResizeObserver 尺寸变化观察器
ResizeObserver
用于监听元素的尺寸变化。 在前端开发中,元素尺寸的变化可能会受到许多因素的影响,例如窗口大小调整、设备方向变化、内部内容变化等。 提供了一种高效的方法来响应这些变化,而不需要频繁使用事件监听器或轮询技术。(窗口我们可以用 addEventListener 监听 resize 事件)
用法
const ro = new ResizeObserver(callback);
ResizeObserver是浏览器原生提供的构造函数,接受一个参数:
- callback:监听元素尺寸发生变化时,会触发回调函数。回调函数接收一个变化对象数组参数,每个对象中包含了尺寸变化的相关信息。
构造函数的返回值是一个尺寸变化观察器实例。实例一共有3个方法:
- observe: 开始监听特定元素
- unobserve: 停止监听特定元素
- disconnect: 关闭监听工作
// 获取目标元素
const ele = document.getElementsByClassName('list-item');
const ro = new ResizeObserver(callback);
function callback(entries){
for(const entry of entries){
const rect = entry.contentRect;
console.log('当前大小', rect);
}
}
// 开始监听
ro.observe(ele);
// 结束监听
ro.unobserve(ele);
// 取消监听所有目标元素
ro.disconnect();
使用场景
- 响应式布局: 随着元素尺寸的变化,动态调整布局或样式。
- 动态容器: 监听内容动态加载的容器的尺寸,以便进行布局调整。
- Canvas 渲染: 根据容器的尺寸变化,动态调整 canvas 的渲染尺寸或内容。
- SVG 调整: 动态调整 SVG 图形的尺寸或形状。
4、PerformanceObserver 性能观察器
PerformanceObserver
是一个 Web API,它可以异步地监听浏览器的performance事件,方便在performance事件触发时作统一处理。用于记录一些时间点、某个时间段、资源加载的耗时等,以便收集和分析页面性能数据。
用法
// 1.创建Performance Observer实例
const po = new PerformanceObserver(callback);
回调函数会在性能指标发生变化时被触发,它接受一个参数:entries,它是一个性能条目对象的数组,每个对象描述了一个性能条目。
// 2.指定要观察的性能条目类型
po.observe({ entryTypes: ['resource', 'paint'] });
性能条目类型是一个配置对象,包括以下属性:
entryTypes:一个数组,包含要观察的性能条目类型
可以填的值包括:
frame: 指的是整个页面,包括页面的导航性能和整体加载时间。它可以监测与整个页面的性能相关的数据。
navigation: 与页面导航和加载时间相关,提供有关导航事件(如页面加载、重定向等)的性能数据。
resource: 与页面中加载的各种资源相关,如图像、脚本、样式表等。它可以监测单个资源的加载性能,包括资源的开始和结束时间,以及其他相关信息。
mark: 与性能标记(mark)相关,性能标记是在代码中设置的时间戳,通常用于记录特定事件的时间,以便后续性能分析。这提供了在页面加载期间创建性能标记的方式。
measure: 与性能测量(measure)相关,性能测量用于测量两个性能标记之间的时间间隔,以获取更详细的性能数据。这提供了测量和分析特定事件之间的时间差的方式。
paint: 与页面绘制性能相关,可以是 "first-paint"(首次绘制)或 "first-contentful-paint"(首次内容绘制)之一。这些指标表示页面呈现的关键时间点,可以帮助我们评估用户视觉上的加载体验。
使用场景
记录某个时间点、某个时间段、资源加载的耗时并上报数据,做性能分析。
更多详细内容:https://juejin.cn/post/7294532494343159843
5、ReportingObserver
ReportingObserver用于监听浏览器报告的事件,例如废弃API,过时特性,网络错误。做监控SDK的同学应该经常能用到,日常业务代码用的比较少。
浏览器还会在一些情况下对网页行为做一些干预(intervention),比如会把占用 cpu 太多的广告的 iframe 删掉。
会在网络比较慢的时候把图片替换为占位图片,点击才会加载。
这些干预都是浏览器做的,会在控制台打印一个报告,但是这些干预或者过时的 api 并不是报错,所以不能用错误监听的方式来拿到,但这些情况对网页来说可能也是很重要的。
用法
const reportingObserver = new ReportingObserver((reports, observer) => {
for (const report of reports) {
console.log(report.body);//上报
}
}, {types: ['intervention', 'deprecation']});
reportingObserver.observe();
使用场景
ReportingObserver 可以监听过时的 api、浏览器干预等报告等的打印,在回调里上报,这些是错误监听无法监听到但对了解网页运行情况很有用的数据。
总结
监听用户的交互行为,我们会用 addEventListener 来监听 click、mousedown、keydown、input 等事件,但对于元素的变化、performance 的记录、浏览器干预行为这些不是用户交互的事件就要用 XxxObserver 的 api 了。
浏览器提供了这 5 种 Observer:
- IntersectionObserver:监听元素可见性变化,常用来做元素显示的数据采集、图片的懒加载
- MutationObserver:监听元素属性和子节点变化,比如可以用来做去不掉的水印
- ResizeObserver:监听元素大小变化
还有两个与元素无关的:
- PerformanceObserver:监听 performance 记录的行为,来上报数据
- ReportingObserver:监听过时的 api、浏览器的一些干预行为的报告,可以让我们更全面的了解网页 app 的运行情况
这些 api 相比 addEventListener 添加的交互事件来说用的比较少,但是在特定场景下都是很有用的。