现代浏览器观察者 Observer API
总体对比
| 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. 分析性能对业务的影响(交互快/慢是否会影响销量) |
Intersection Observer,交叉观察者
IntersectionObserver 可以监听一个元素和可视区域相交部分的比例,然后在可视比例达到某个阈值的时候触发回调。
浏览器兼容性

语法
ts
var observer = new IntersectionObserver(callback[, options]);
- callback 是一个回调函数,里面返回监听目标元素的实时数据组成的数组
- time 时间戳
- rootBounds 根元素的位置信息
- boundingClientRect 目标元素的位置信息
- intersectionRect 交叉部分的位置信息
- intersectionRatio 目标元素的可见比例,看下图示
- target 等
- options 是一些配置
- root 目标元素的祖先元素,即该元素必须是目标元素的直接或间接父级
- rootMargin 一个在计算交叉值时添加至 root 的边界盒中的一组偏移量,写法类似 CSS 的 margin
- threshold 规定了一个监听目标与边界盒交叉区域的比例值,可以是一个具体的数值或是一组 0.0 到 1.0 之间的数组

开始监听元素: observer.observe(target)
停止对某目标的监听: observer.unobserve(target)
终止对所有目标的监听: observer.disconnect()
polyfill
pnpm add intersection-observer
使用
入口文件引入即可
import 'intersection-observer'
示例
vue
<script setup>
const intersectionObserver = new IntersectionObserver(
function (entries) {
console.log('info:')
entries.forEach(item => {
console.log(item.target, item.intersectionRatio)
})
},
{
threshold: [0.5, 1],
}
)
</script>
<template>
<div id="box1">BOX111</div>
<div id="box2">BOX222</div>
</template>
<style>
#box1,
#box2 {
width: 100px;
height: 100px;
background: blue;
color: #fff;
position: relative;
}
#box1 {
top: 500px;
}
#box2 {
top: 800px;
}
</style>
创建一个 IntersectionObserver 对象,监听 box1 和 box2 两个元素,当可见比例达到 0.5 和 1 的时候触发回调。
可以看到元素 box1 和 box2 在可视范围达到一半(0.5)和全部(1)的时候分别触发了回调。
数据采集的时候,希望知道某个元素是否是可见的,什么时候可见的,就可以用这个 api 来监听,还有做图片的懒加载的时候,可以当可视比例达到某个比例再触发加载。
Mutation Observer,变动观察者
MutationObserver 可以监听对元素的属性的修改、对它的子节点的增删改。
兼容性

语法
ts
var observer = new MutationObserver(callback)
开始监听
ts
observer.observe(target, config)
- config 填写需要监听属性
- attributes 布尔类型 属性的变动
- childList 布尔类型 子节点的变动(指新增,删除或者更改)
- characterData 布尔类型 节点内容或节点文本的变动。
- subtree 布尔类型 是否将该观察器应用于该节点的所有后代节点
- attributeOldValue 布尔类型 观察 attributes 变动时,是否需要记录变动前的属性值
- characterDataOldValue 布尔类型 观察 characterData 变动时,是否需要记录变动前的值
- attributeFilter 数组 需要观察的特定属性(比如['class','src'])
终止对所有目标的监听: observer.disconnect()
takeRecords(): 从 MutationObserver 的通知队列中删除所有待处理的通知,并将它们返回到 MutationRecord 对象的新 Array 中。
polyfill
如果你的浏览器不需要兼容 IE9,IE10 浏览器,推荐使用 MutationObserver 实现 DOM 变化的检测。
如果你的项目需要兼容 IE9,IE10 浏览器,同时想要实现对 DOM 变化的检测,则可以试试 Mutation events。

Mutation events 语法上相对简单易懂很多。
你就认为是和'click', 'mouseover'一样的 DOM 事件用就好了。
支持的事件列表如下:
- DOMAttrModified (Chrome/Safari 不支持)
- DOMAttributeNameChanged
- DOMCharacterDataModified
- DOMElementNameChanged
- DOMNodeInserted
- DOMNodeInsertedIntoDocument (IE 不支持)
- DOMNodeRemoved
- DOMNodeRemovedFromDocument (IE 不支持)
- DOMSubtreeModified
具体描述见下表(IE 不支持的两个我们忽略,这个就算没兼容性问题和很少用到):
| 事件名称 | 事件描述 |
|---|---|
| DOMAttrModified | DOM 属性发生修改 |
| DOMAttributeNameChanged | DOM 属性名发生变化 |
| DOMCharacterDataModified | DOM 文本数据发生修改 |
| DOMElementNameChanged | DOM 元素名发生变化 |
| DOMNodeInserted | DOM 节点插入 |
| DOMNodeRemoved | DOM 节点删除 |
| DOMSubtreeModified | DOM 子元素修改 |
使用例子:
js
element.addEventListener(
'DOMNodeInserted',
function (event) {
// event.target就是依次插入的DOM节点
},
false
)
Chrome/Safari 不支持 DOMAttrModified 的处理
Object.defineProperty,可以方便对自定义属性的变化进行检测。
ts
// 重新定义rows属性
Object.defineProperty(ell, 'rows', {
writeable: true,
enumerable: true,
get: function () {
return this.getAttribute('rows')
},
set: function (rows) {
this.setAttribute('rows', rows)
// rows变化了,重渲染
this.render()
},
})
Object.defineProperty 语法如下:
ts
Object.defineProperty(obj, prop, descriptor)
示例
vue
<script setup>
setTimeout(() => {
box.style.background = 'red'
}, 2000)
setTimeout(() => {
const dom = document.createElement('button')
dom.textContent = '东东东'
box.appendChild(dom)
}, 3000)
setTimeout(() => {
document.querySelectorAll('button')[0].remove()
}, 5000)
onMounted(() => {
const mutationObserver = new MutationObserver(mutationsList => {
console.log(mutationsList)
})
mutationObserver.observe(box, {
attributes: true,
childList: true,
})
})
</script>
<template>
<div id="box"><button>光</button></div>
</template>
<style>
#box {
width: 100px;
height: 100px;
background: blue;
position: relative;
}
</style>
2s 的时候修改背景颜色为红色,3s 的时候添加一个 button 的子元素,5s 的时候删除第一个 button。
第一次改变的是 attributes,属性是 style:

第二次改变的是 childList,添加了一个节点:

第三次也是改变的 childList,删除了一个节点:

比如文章水印被人通过 devtools 去掉了,那么就可以通过 MutationObserver 监听这个变化,然后重新加上,让水印去不掉。
Resize Observer,尺寸观察者
元素可以用 ResizeObserver 监听大小的改变,当 width、height 被修改时会触发回调。
浏览器兼容性

语法
ts
var observer = new ResizeObserver(callback)
observer.observe(target)
开始监听元素: observer.observe(target)
停止对某目标的监听: observer.unobserve(target)
终止对所有目标的监听: observer.disconnect()
触发回调后的第一个参数是一个 ResizeObserverEntry 对象。这里的 entry.target 是 DOM 节点本身,而 entry.contentRect 是一个对象,包含了节点的位置属性,如 width, height, left, right, bottom, left, x, y 等。
- width:指元素本身的宽度,不包含 padding,border 值
- height:指元素本身的高度,不包含 padding,border 值
- top:指 padidng-top 的值
- left:指 padding-left 的值
- right:指 left + width 的值
- bottom: 值 top + height 的值
- x:大小与 top 相同
- y:大小与 left 相同
polyfill
pnpm add resize-observer-polyfill
ts
import ResizeObserver from 'resize-observer-polyfill'
const ro = new ResizeObserver((entries, observer) => {
for (const entry of entries) {
const { left, top, width, height } = entry.contentRect
console.log('Element:', entry.target)
console.log(`Element's size: ${width}px x ${height}px`)
console.log(`Element's paddings: ${top}px ; ${left}px`)
}
})
ro.observe(document.body)
示例
vue
<script setup>
onMounted(() => {
const box = document.querySelector('#box')
const resizeObserver = new ResizeObserver(entries => {
console.log('当前大小', entries)
})
resizeObserver.observe(box)
setTimeout(() => {
box.style.width = '200px'
}, 3000)
})
</script>
<template>
<div id="box"></div>
</template>
<style>
#box {
width: 100px;
height: 100px;
background: blue;
}
</style>
大小变化被监听到了,看下打印的信息

可以拿到元素和它的位置、尺寸。
Performance Observer,性能观察者
PerformanceObserver 用于监听记录 performance 数据的行为,一旦记录了就会触发回调,这样我们就可以在回调里把这些数据上报。
比如 performance 可以用 mark 方法记录某个时间点:
ts
performance.mark('registered-observer')
用 measure 方法记录某个时间段:
ts
performance.measure('button clicked', 'from', 'to')
后两个个参数是时间点,不传代表从开始到现在。
我们可以用 PerformanceObserver 监听它们:
html
<html>
<body>
<button onclick="measureClick()">Measure</button>
<img src="https://p9-passport.byteacctimg.com/img/user-avatar/4e9e751e2b32fb8afbbf559a296ccbf2~300x300.image" />
<script>
const performanceObserver = new PerformanceObserver(list => {
list.getEntries().forEach(entry => {
console.log(entry) // 上报
})
})
performanceObserver.observe({ entryTypes: ['resource', 'mark', 'measure'] })
performance.mark('registered-observer')
function measureClick() {
performance.measure('button clicked')
}
</script>
</body>
</html>
创建 PerformanceObserver 对象,监听 mark(时间点)、measure(时间段)、resource(资源加载耗时) 这三种记录时间的行为。
然后我们用 mark 记录了某个时间点,点击 button 的时候用 measure 记录了某个时间段的数据,还加载了一个图片。
当这些记录行为发生的时候,希望能触发回调,在里面可以上报。
mark

resource

measure
