JS中的观察者们 —— 四种 Observers
今天跟大家一块学习一下JS中的几个观察者(Observer) API,他们是 ——
- Intersection Observer
- Mutation Observer
- Resize Observer
- Performance Observer
Intersection Observer
当你想监听某个元素,当它在视口中可见的时候希望可以得到通知,这个API就是最佳的选择了。以往我们的做法是绑定容器的scroll事件,或者设定时器不停地调用getBoundingClientRect() 获取元素位置, 这样做的性能会很差,因为每次获取元素的位置都会引起整个布局的重新计算。还有一个场景是,如果你的元素被放在iframe里,如一些广告,想要知道他们何时出现几乎是不可能的。
现在,我们完全可以把这些工作交给IntersectionObserver了。
怎么用?
实例化一个观察器:
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)
这两步之后,就可以在callback里补全业务代码了。
此外,还有两个方法:
- 停止对某目标的监听
observer.unobserve(target)
- 终止对所有目标的监听
observer.disconnect()
demo ?↓
做一个视频流的简单demo,当视频滚动到全部出现在屏幕的时候播放,并暂停其他“滚出”屏幕的或者还未“滚进来”的视频。
(滚动看效果,括号里显示的是每个元素在观察视口的可见比例)
主要代码:
methods: {
reserveCallback (entries) {
let ratio = entries[0].intersectionRatio,
$target = entries[0].target;
if(1 - ratio <= 0.03) {
this.onPlay($target);
}
},
onPlay ($target) {
//播放视频
},
onPause ($target) {
//暂停视频
},
addObserver ($targets) {
for(let i = 0, len = $targets.length; i //开始监听
this.observer.observe($targets[i]);
}
}
},
mounted () {
let $referenceBox = document.querySelector('.js-content'),
$targets = document.querySelectorAll('.js-item');
//实例化观察器
this.observer = new IntersectionObserver(this.reserveCallback, {
root: $referenceBox,
rootMargin: '0px',
threshold: [0.7, 0.8, 0.9, 1]
});
this.addObserver($targets);
}
上面的demo也有很大的不足之处:对所有的视频元素都进行了监听,并且对“消失”的视频没有关掉观察器,当视频数增多时,势必会引起性能上的问题。如果真的应用在业务中还需要进一步的优化。
其他应用
除了上面提到的广告展示,还可以做懒加载:在列表的底部设置一个用来监听的元素,当它出现时,加载更多的内容,同时改变该监听元素的位置到底部继续监听。
Mutation Observer
当我们想知道某个元素在某个时候发生了具体哪些变化时,MutationObserver便是最佳选择了。
怎么用
实例化一个观察器:
var observer = new MutationObserver(callback);
开始监听:
observer.observe(target, config);
- config 填写需要监听属性
- attributes 布尔类型 属性的变动
- childList 布尔类型 子节点的变动(指新增,删除或者更改)
- characterData 布尔类型 节点内容或节点文本的变动。
- subtree 布尔类型 是否将该观察器应用于该节点的所有后代节点
- attributeOldValue 布尔类型 观察attributes变动时,是否需要记录变动前的属性值
- characterDataOldValue 布尔类型 观察characterData变动时,是否需要记录变动前的值
- attributeFilter 数组 需要观察的特定属性(比如['class','src'])
下面是一个简单的demo,任何对节点的操作都会收到MutationObserver API的通知。
demo ?↓
主要代码:
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);
}
Resize Observer
从名字就可以知道该API是干嘛的了:监听元素的尺寸变化。
之前为了监听元素的尺寸变化,都将侦听器附加到window中的resize事件。对于不受窗口变化影响的元素就没那么简单了。现在我们可以使用该API轻松的实现。
怎么用
同样只需要两步:
var observer = new ResizeObserver(callback);
observer.observe(target);
但是它的触发也是有条件的,下面是触发和不触发的条件:
触发
- 1.元素被插入或移除时触发
- 2.元素display从显示变成 none 或相反过程时触发
不触发
- 1.对于不可替换内联元素不触发
- 2.CSS transform 操作不触发
demo ?↓
拖动右下角变换元素尺寸或点击随机按钮设置随机尺寸,都会收到通知(里面的小星星转动速度变化)。而通过transform变化视觉上的尺寸时是没有通知的。
遗憾的是该API仍处于实验阶段,好多浏览器没有实现。
不过,由于MutationObserver已经被大部分浏览器支持,且有polyfill的支持, 我们可以轻松的利用他来代替ResizeObserver。
推荐阅读
- JavaScript中的ResizeObserver
- Resize Observer 1
- A Look at the Resize Observer JavaScript API
- THE RESIZE OBSERVER EXPLAINED
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] });
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 各种与资源加载相关的信息
相较之前的各种操作,现在我们代码仅需要像这样就可以了——
const observer = new PerformanceObserver((list) => {
let output;
for (const item of list.getEntries()) {
//业务代码
}
});
observer.observe({
//按需要填写
entryTypes: ['mark', 'measure', 'longtask', 'paint', 'navigation', 'resource']
});
上述内容如有错误,欢迎不吝赐教?。