** JS中的观察者们 **
JS中的几个观察者(Observer) API
- Intersection Observer
- Mutation Observer
- Resize Observer
- Performance Observer
1、IntersectionObserver
当你想监听某个元素,当它在视口中可见的时候希望可以得到通知,这个API就是最佳的选择了。以往我们的做法是绑定容器的scroll事件,或者设定时器不停地调用getBoundingClientRect() 获取元素位置, 这样做的性能会很差,因为每次获取元素的位置都会引起整个布局的重新计算。还有一个场景是,如果你的元素被放在iframe里,如一些广告,想要知道他们何时出现几乎是不可能的。
现在,我们完全可以把这些工作交给IntersectionObserver了,IntersectionObserver 提供了一个高效的方式来检测元素的进入和离开视口,能够应用于多种场景,有助于提升性能和用户体验。你可以根据具体场景来选择合适的应用方法和思路。
1.1、怎么用?
实例化一个观察器:
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)
- 终止对所有目标的监听
1.2、demo 🌰↓
做一个视频流的简单demo,当视频滚动到全部出现在屏幕的时候播放,并暂停其他“滚出”屏幕的或者还未“滚进来”的视频。
(滚动看效果,括号里显示的是每个元素在观察视口的可见比例)
主要代码:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Intersection Observer Video Demo</title><style>body {height: 200vh; /* 让页面变长,以便可以滚动 */display: flex;flex-direction: column;align-items: center;}video {width: 80%;margin: 20px 0;}</style>
</head>
<body><video src="video1.mp4" muted></video><video src="video2.mp4" muted></video><video src="video3.mp4" muted></video><script>// 选择所有的视频元素const videos = document.querySelectorAll('video');// 创建 IntersectionObserver 实例const observer = new IntersectionObserver((entries) => {entries.forEach(entry => {const video = entry.target;if (entry.isIntersecting) {// 当视频进入视口时播放video.play();} else {// 当视频滚出视口时暂停video.pause();}});}, { threshold: 1.0 }); // 设置阈值为 1.0,表示元素完全可见// 观察每个视频元素videos.forEach(video => {observer.observe(video);});</script>
</body>
</html>
创建 IntersectionObserver 实例,传入回调函数,处理每次的交集变动。
回调函数检查每个视频的状态:如果该视频完全可见,则播放;如果滚出视口,则暂停。
使用 threshold: 1.0 确保视频在完全可见的情况下才会播放。
1.3、其他应用
(1)无限滚动
const sentinel = document.querySelector('#sentinel');const loadMoreContent = () => {// 这里加载更多内容的逻辑
};const sentinelObserver = new IntersectionObserver((entries) => {if (entries[0].isIntersecting) {loadMoreContent();}
});sentinelObserver.observe(sentinel);
(2)制作“返回顶部”按钮
const backToTopButton = document.querySelector('#backToTop');const buttonObserver = new IntersectionObserver((entries) => {if (entries[0].isIntersecting) {backToTopButton.style.display = 'none';} else {backToTopButton.style.display = 'block';}
});buttonObserver.observe(document.querySelector('#triggerElement'));
1.4、参考资料
- Intersection Observer
- IntersectionObserver’s Coming into View
- Observing Intersection Observers
- IntersectionObserver
- IntersectionObserver polyfill
2、MutationObserver
MutationObserver 接口提供了监视对 DOM 树所做更改的能力。它被设计为旧的 Mutation Events 功能的替代品,该功能是 DOM3 Events 规范的一部分。
2.1、怎么用?
实例化一个观察器:
var observer = new MutationObserver(callback);
开始监听:
observer.observe(target, config);
- config 填写需要监听属性
- attributes 布尔类型 属性的变动
- childList 布尔类型 子节点的变动(指新增,删除或者更改)
- characterData 布尔类型 节点内容或节点文本的变动。
- subtree 布尔类型 是否将该观察器应用于该节点的所有后代节点
- attributeOldValue 布尔类型 观察attributes变动时,是否需要记录变动前的属性值
- characterDataOldValue 布尔类型 观察characterData变动时,是否需要记录变动前的值
- attributeFilter 数组 需要观察的特定属性(比如[‘class’,‘src’])
下面是一个简单的demo,任何对节点的操作都会收到MutationObserver API的通知。
2.2、demo 🌰↓
// 选择要观察的目标节点
const targetNode = document.getElementById('myElement');// 配置观察选项
const config = { attributes: true, childList: true, subtree: true };// 创建观察者实例并定义回调函数
const observer = new MutationObserver((mutationsList) => {for (const mutation of mutationsList) {if (mutation.type === 'childList') {console.log('子节点已更改。');} else if (mutation.type === 'attributes') {console.log('属性已更改。');}}
});// 开始观察
observer.observe(targetNode, config);// 示例:更改目标节点
targetNode.appendChild(document.createElement('div'));
targetNode.setAttribute('data-new-attr', 'value');
这个示例中,当 myElement 的子节点或属性发生变化时,回调函数会被触发并记录变动。
2.3、参考资料
- MutationObserver
- Detect DOM changes with Mutation Observers
- Mutation Observer API
- Getting to Know Mutation Observers
3、ResizeObserver
ResizeObserver 是一个非常有用的 API,用于监测元素的大小变化。它适合在全屏模式下、响应式设计或动态内容时自动调整布局或做出其他反应
之前为了监听元素的尺寸变化,都将侦听器附加到window中的resize事件。对于不受窗口变化影响的元素就没那么简单了。 现在我们可以使用该API轻松的实现。
3.1、怎么用?
同样只需要两步:
var observer = new ResizeObserver(callback);
observer.observe(target);
但是它的触发也是有条件的,下面是触发和不触发的条件:
-
触发
- 1.元素被插入或移除时触发
- 2.元素display从显示变成 none 或相反过程时触发
-
不触发
- 1.对于不可替换内联元素不触发
- 2.CSS transform 操作不触发
3.2、demo 🌰↓
(1)动态调整字体大小
你可以根据容器的尺寸动态调整文本的字体大小。
<div id="container" style="resize: both; overflow: auto; width: 200px; height: 100px; border: 1px solid black;"><p id="text">Resize me!</p>
</div><script>
const container = document.getElementById('container');
const text = document.getElementById('text');const resizeObserver = new ResizeObserver(entries => {for (let entry of entries) {const width = entry.contentRect.width;text.style.fontSize = `${Math.max(16, width / 10)}px`; // 计算字体大小}
});resizeObserver.observe(container);
</script>
3.3、推荐阅读
- 浏览器兼容性
- JavaScript中的ResizeObserver
- Resize Observer 1
- A Look at the Resize Observer JavaScript API
- THE RESIZE OBSERVER EXPLAINED
4、PerformanceObserver
PerformanceObserver 是用于监控性能指标的 Web API,可以帮助开发者收集和分析网页性能数据,它是个相对比较复杂的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
4.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']
});
PerformanceObserver 提供了一个非常灵活的方式来收集和监控性能数据,开发者可以根据应用需求选择合适的性能指标进行分析。通过这些数据,开发者可以更好地优化用户体验,并做出相应的性能改进。
4.2、demo 🌰↓
(1) 监测页面加载性能
可以监测页面的加载时间和相关性能指标,例如 navigation
和resource
。
const performanceObserver = new PerformanceObserver((list) => {list.getEntries().forEach((entry) => {if (entry.entryType === 'navigation') {console.log(`页面加载时间: ${entry.loadEventEnd - entry.startTime} ms`);}});
});performanceObserver.observe({ entryTypes: ['navigation'] });
(2) 监测资源加载时间
跟踪特定资源(如图片、脚本、样式表)的加载时间。
const resourceObserver = new PerformanceObserver((list) => {list.getEntries().forEach((entry) => {if (entry.initiatorType === 'img') {console.log(`图片 ${entry.name} 加载时间: ${entry.duration} ms`);}});
});resourceObserver.observe({ entryTypes: ['resource'] });
(2) 监测长任务
监控大于 50 毫秒的任务,以识别可能影响用户体验的长时间运行的 JavaScript 任务。
const longTaskObserver = new PerformanceObserver((list) => {list.getEntries().forEach((entry) => {console.warn(`长任务: ${entry.name},持续时间: ${entry.duration} ms`);});
});longTaskObserver.observe({ entryTypes: ['longtask'] });
4.3、参考资料
- PerformanceObserver()
- Different Types Of Observers Supported By Modern Browsers
参考链接
https://xiaotianxia.github.io/blog/vuepress/js/four_kinds_of_observers.html