Observer
前言
前段时间在研究前端异常监控/埋点平台的实现。
在思考方案时,想到了浏览器自带的观察者以及页面生命周期API 。
于是在翻查资料时意外发现,原来现代浏览器支持多达四种不同类型的观察者:
Intersection Observer,交叉观察者。Mutation Observer,变动观察者。Resize Observer,视图观察者。Performance Observer,性能观察者
| IntersectionObserver | MutationObserver | ResizeObserver | PerformanceObserver | |
|---|---|---|---|---|
| 用途 | 观察一个元素是否在视窗可见 | 观察DOM中的变化 | 观察视口大小的变化 | 监测性能度量事件 |
| 方法 | observe() disconnect() takeRecords() | observe() disconnect() takeRecords() unobserve() | observe() disconnect() unobserve() | observe() disconnect() takeRecords() |
| 取代 | Dom Mutation events | getBoundingClientRect() 返回元素的大小及其相对于可视窗口的位置 Scroll 和 Resize 事件 | Resize 事件 | Performance 接口 |
| 用途 | 1. 无限滚动 2. 图片懒加载 3. 兴趣埋点 4. 控制动画/视频执行(性能优化) | 1. 更高性能的数据绑定及响应 2. 实现视觉差滚动 3. 图片预加载 4. 实现富文本编辑器 | 1. 更智能的响应式布局(取代@media) 2. 响应式组件 | 1. 更细颗粒的性能监控 2. 分析性能对业务的影响(交互快/慢是否会影响销量) |
1. IntersectionObserver:交叉观察者
IntersectionObserver接口,提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法,祖先元素与视窗(viewport)被称为根(root)
1. 出现的意义
想要计算Web页面的元素的位置,非常依赖于DOM状态的显式查询。但这些查询是同步的,会导致昂贵的样式计算开销(重绘和回流),且不停轮询会导致大量的性能浪费。
于是便发展了以下的几种方案:
- 构建DOM和数据的自定义预加载和延迟加载。
- 实现了数据绑定的高性能滚动列表,该列表加载和呈现数据集的子集。
- 通过
scroll等事件或通过插件的形式,计算真实元素可见性。
而它们都有几项共同特点:
- 基本实现形式都是查询各个元素相对与某些元素(全局视口)的“被动查询”。
- 信息可以异步传递(例如从另一个线程传递),且没有统一捕获错误的处理。
web平台支持匮乏,各有各家的处理。需要开发人员消耗大量精力兼容。
2. IntersectionObserver的优势
Intersection Observer API通过为开发人员提供一种新方法来异步查询元素相对于其他元素或全局视口的位置,从而解决了上述问题:
- 异步处理消除了昂贵的
DOM和样式查询,连续轮询以及使用自定义插件的需求。 - 通过消除对这些方法的需求,可以使应用程序显着降低
CPU,GPU和资源成本。
3. IntersectionObserver基本使用
使用IntersectionObserver API主要需要三个步骤:
- 创建观察者
- 定义回调事件
- 定义要观察的目标对象
1.创建观察者
const options = {
root: document.querySelector('.scrollContainer'),
rootMargin: '0px',
threshold: [0.3, 0.5, 0.8, 1] }
const observer = new IntersectionObserver(handler, options)
这几个参数用大白话解释就是:
root:指定一个根元素rootMargin:使用类似于设置CSS边距的语法来指定根边距(根元素的观察影响范围)threshold:阈值,可以为数组。[0.3]意味着,当目标元素在根元素指定的元素内可见30%时,调用处理函数。
2. 定义回调事件
当目标元素与根元素通过阈值相交时,就会触发回调函数。
function handler (entries, observer) {
entries.forEach(entry => {
// 每个成员都是一个IntersectionObserverEntry对象。
// 举例来说,如果同时有两个被观察的对象的可见性发生变化,entries数组就会有两个成员。
// entry.boundingClientRect
// entry.intersectionRatio
// entry.intersectionRect
// entry.isIntersecting
// entry.rootBounds
// entry.target
// entry.time
});
}
- time 时间戳
- rootBounds 根元素的位置
- boundingClientRect 目标元素的位置信息
- intersectionRect 交叉部分的位置信息
- intersectionRatio 目标元素的可见比例,看下图示
- target。

3. 定义要观察的目标对象
任何目标元素都可以通过调用.observer(target)方法来观察。
const target = document.querySelector(“.targetBox”);
observer.observe(target);
此外,还有两个方法:
停止对某目标的监听
observer.unobserve(target)
终止对所有目标的监听
observer.disconnect()
4. 例子1:图片懒加载
HTML:
<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">
<!-- more images -->
脚本:
let observer = new IntersectionObserver(
(entries, observer) => {
entries.forEach(entry => {
/* 替换属性 */
entry.target.src = entry.target.dataset.src;
observer.unobserve(entry.target);
});
},
{rootMargin: "0px 0px -200px 0px"});
document.querySelectorAll('img').forEach(img => { observer.observe(img) });
上述例子表示 仅在到达视口距离底部200px视加载图片。