# Intersection Observer
当你想监听某个元素,当它在视口中可见的时候希望可以得到通知,这个API就是最佳的选择了。以往我们的做法是绑定容器的scroll事件,或者设定时器不停地调用getBoundingClientRect() 获取元素位置, 这样做的性能会很差,因为每次获取元素的位置都会引起整个布局的重新计算。还有一个场景是,如果你的元素被放在iframe里,如一些广告,想要知道他们何时出现几乎是不可能的。
IntersectionObserver接口 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法。祖先元素与视窗(viewport)被称为根(root)
通俗点说就是:IntersectionObserver是用来监听某个元素与视口的交叉状态的。交叉状态是什么呢?请看下图,一开始整个元素都在视口内,那么元素与视口的交叉状态就是100%,而我往下滚动,元素只有一半显示在视口里,那么元素与视口的交叉状态为50%:
# 用法
// 接收两个参数 callback option
var io = new IntersectionObserver(callback, option);
// 开始观察(可观察多个元素)
io.observe(document.getElementById('example1'));
io.observe(document.getElementById('example2'));
// 停止观察某个元素
io.unobserve(element);
// 关闭观察器
io.disconnect();
2
3
4
5
6
7
8
9
10
11
12
13
- callback
callback一般有两种触发情况。一种是目标元素刚刚进入视口(可见),另一种是完全离开视口(不可见)
var io = new IntersectionObserver(
entries => {
console.log(entries);
}
);
2
3
4
5
callback函数的参数(entries)是一个数组,每个成员都是一个IntersectionObserverEntry对象。举例来说,如果同时有两个被观察的对象的可见性发生变化,entries数组就会有两个成员。
IntersectionObserverEntry对象
- time:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒
- target:被观察的目标元素,是一个 DOM 节点对象
- rootBounds:根元素的矩形区域的信息,getBoundingClientRect()方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回null
- boundingClientRect:目标元素的矩形区域的信息
- intersectionRect:目标元素与视口(或根元素)的交叉区域的信息
- intersectionRatio:目标元素的可见比例,即intersectionRect占boundingClientRect的比例,完全可见时为1,完全不可见时小于等于0
{
time: 3893.92,
rootBounds: ClientRect {
bottom: 920,
height: 1024,
left: 0,
right: 1024,
top: 0,
width: 920
},
boundingClientRect: ClientRect {
// ...
},
intersectionRect: ClientRect {
// ...
},
intersectionRatio: 0.54,
target: element
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- option
讲讲第二个参数option里比较重要的两个属性:threshold和root
首先讲讲threshold:
threshold属性决定了什么时候触发回调函数。它是一个数组,每个成员都是一个门槛值,默认为[0],即交叉比例(intersectionRatio)达到0时触发回调函数。
new IntersectionObserver(
entries => {/* ... */},
{
threshold: [0, 0.25, 0.5, 0.75, 1],
root: document.getElementById('#container')
}
);
//用户可以自定义这个数组。比如,[0, 0.25, 0.5, 0.75, 1]就表示当目标元素 0%、25%、50%、75%、100% 可见时,会触发回调函数。
//再说说root:
//IntersectionObserver API 支持容器内滚动。root属性指定目标元素所在的容器节点(即根元素)。注意,容器元素必须是目标元素的祖先节点。
2
3
4
5
6
7
8
9
10
11
12
- 完整例子
body {
height: 3000px;
width: 3000px;
}
#box1 {
width: 300px;
height: 300px;
background-color: red;
margin-top: 100px;
margin-left: 300px;
}
#box2 {
width: 300px;
height: 300px;
background-color: red;
margin-top: 100px;
margin-left: 300px;
}
<div id="box1"></div>
<div id="box2"></div>
const io = new IntersectionObserver(entries => {
console.log(entries)
}, {
threshold: [0, 0.25, 0.5, 0.75, 1]
// root: xxxxxxxxx
})
io.observe(document.getElementById('box1'))
io.observe(document.getElementById('box2'))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
- 使用场景
- 可以像getBoundingClientRect那样判断元素是否在视口里,并且好处是,不会引起重绘回流
- 同理,有了第一点功能,就可以做懒加载和无限滚动功能了
# Mutation Observer
当我们想知道某个元素在某个时候发生了具体哪些变化时,MutationObserver便是最佳选择了。
var observer = new MutationObserver(callback);
observer.observe(target, config);
2
3
config 填写需要监听属性
- attributes 布尔类型 属性的变动
- childList 布尔类型 子节点的变动(指新增,删除或者更改)
- characterData 布尔类型 节点内容或节点文本的变动。
- subtree 布尔类型 是否将该观察器应用于该节点的所有后代节点
- attributeOldValue 布尔类型 观察attributes变动时,是否需要记录变动前的属性值
- characterDataOldValue 布尔类型 观察characterData变动时,是否需要记录变动前的值
- attributeFilter 数组 需要观察的特定属性(比如['class','src'])
例子主要代码
methods: {
observerCallBack (mutations) {
//do log
},
onAddAttr () {
// toggle attribute 'd'
}
},
mounted () {
this.$list = document.querySelector('.js-list');
let config = {
attributes: true,
childList: true,
characterData: true,
subtree: true
};
let observer = new MutationObserver(this.observerCallBack);
observer.observe(this.$list, config);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Resize Observer
从名字就可以知道该API是干嘛的了:监听元素的尺寸变化。
之前为了监听元素的尺寸变化,都将侦听器附加到window中的resize事件。对于不受窗口变化影响的元素就没那么简单了。 现在我们可以使用该API轻松的实现。
var observer = new ResizeObserver(callback);
observer.observe(target);
2
触发
- 元素被插入或移除时触发
- 元素display从显示变成 none 或相反过程时触发
不触发
- 对于不可替换内联元素不触发
- CSS transform 操作不触发
遗憾的是该API仍处于实验阶段,好多浏览器没有实现。 由于MutationObserver已经被大部分浏览器支持,且有polyfill的支持, 我们可以轻松的利用他来代替ResizeObserver。
# Performance Observer
PerformanceObserver 是个相对比较复杂的API,用来监控各种性能相关的指标。 该API由一系列API组成:
Performance Timeline Level 2
Paint Timing 1
Navigation Timing Level 2
User Timing Level 3
Resource Timing Level 2
Long Tasks API 1
如何使用
var observer = new PerformanceObserver(callback);
observer.observe({ entryTypes: [entryTypes] });
//监控相关指标
const observer = new PerformanceObserver((list) => {
let output;
for (const item of list.getEntries()) {
//业务代码
}
});
observer.observe({
//按需要填写
entryTypes: ['mark', 'measure', 'longtask', 'paint', 'navigation', 'resource']
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
entryTypes: 需要监控的指标名,这些指标都可以通过 performance.getEntries() 获取到,此外还可以通过 performance.getEntriesByName() 、performance.getEntriesByType() 分别针对 name 和 entryType 来过滤。
- mark 获取所有通过 performance.mark(markName) 做的所有标记
- measure 获取通过 performance.measure(measureName, markName_start, markName_end) 得到的所有测量值
- longtask 监听长任务(超过50ms 的任务)(不足:只能监控到长任务的存在,貌似不能定位到具体任务)
- paint 获取绘制相关的性能指标,分为两种:“first-paint”、“first-contentful-paint”
- navigation 各种与页面有关的时间,可通过 performance.timing 获取
- resource 各种与资源加载相关的信息