现代浏览器观察者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接口 |
用途 |
|
|
|
|
IntersectionObserver 交叉观察者
1.创建观察者
const options = { root:document.getElementById('zjy'), 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 时间戳 }); }
可以在handler中做自己想做的事,例如:
entry.target.src = entry.target.dataset.src; observer.unobserve(entry.target);
3.定义要观察的目标对象
const domArr = document.querySelectorAll(“img”); domArr.forEach((t)=>{ observer.observe(t); })
MutationObserver 变动观察者
接口提供了对DOM树所做更改的能力。它被设计而为旧的MutationEvents功能的替代品,该功能是DOM3 Events 规范的一部分。
使用方式
1.定义观察者
let observer new MutationObserver(cb)
2.定义回调函数
function cb(mutations,observer){ mutations.forEach((mutation)=>{
//输出变更记录 console.log(mutation) }) }
每个mutation都对应一个MutationRecord对象,记录着DOM每次发生变化的变化记录。
MutationRecord对象包含的DOM相关信息如下:
属性 | 意义 |
type | 观察的变动类型(attribute、characterData或者childList) |
target | 发生变动的DOM节点 |
addedNodes | 新增的DOM节点 |
removedNodes | 删除的DOM节点 |
previousSibling | 前一个同级节点,如果没有则返回null |
nextSibling | 下一个同级节点,如果没有则返回null |
attributeName | 发生变动的属性。如果设置了attributeFilter,则只返回预先指定的属性 |
oldValue | 变动前的值。这个属性只对attribute和characterData变动有效,如果发生childList变动,只返回null |
3.定义要观察的目标对象
observer.observe(content, { attributes: true, // Boolean - 观察目标属性的改变 characterData: true, // Boolean - 观察目标数据的改变(改变前的数据/值) childList: true, // Boolean - 观察目标子节点的变化,比如添加或者删除目标子节点,不包括修改子节点以及子节点后代的变化 subtree: true, // Boolean - 目标以及目标的后代改变都会观察 attributeOldValue: true, // Boolean - 表示需要记录改变前的目标属性值 characterDataOldValue: true, // Boolean - 设置了characterDataOldValue可以省略characterData设置 // attributeFilter: ['src', 'class'] // Array - 观察指定属性 });
- 第一参数:被观察的
DOM
节点。 - 第二参数:配置需要观察的变动项
options
。
ResizeObserver
视图观察者
ResizeObserver API
是一个新的JavaScript API
,与IntersectionObserver API
非常相似,它们都允许我们去监听某个元素的变化。
使用方式与MutationObserver大致相同 简单描述一下
//创建观察者 let observer = new ResizeObserver(callback); //定义回调函数 function callback(entries){ entries.forEach((entry)=>{ console.log(entry) }) } //定义要观察的目标对象 observer.observe(document.body)
介绍一下entry对象,包含两个属性contentRect和target
contentRect一些位置信息
属性 | 作用 |
bottom | top + height 的值 |
height | 元素本身的高度,不包含padding、border的值 |
left | padding-left的值 |
right | left + width 的值 |
top | padding-top 的值 |
width | 元素本身的宽度,不包含padding 、border的值 |
x | 大小与top相同 |
y | 大小与left相同 |
PerformanceObserver
性能观察者
这是一个浏览器和Node.js
里都存在的API,采用相同W3C
的Performance Timeline
规范
- 在浏览器中,我们可以使用 window 对象取得
window.performance
和window.PerformanceObserver
。 - 而在
Node.js
程序中需要perf_hooks
取得性能对象
const { PerformanceObserver, performance } = require('perf_hooks');
//创建观察者 let observer = new PerformanceObserver(callback); //定义回调函数事件 function callback(list, observer){ const entries = list.getEntries(); entries.forEach((entry) => { console.log(“Name: “ + entry.name + “, Type: “ + entry.entryType + “, Start: “ + entry.startTime + “, Duration: “ + entry.duration + “\n”); }); } //定义要观察的目标对象 observer.observe({entryTypes: ["entryTypes"]});
每一个list
都是一个完整的PerformanceObserverEntryList
对象,包含三个方法getEntries
、getEntriesByType
、getEntriesByName
方法 | 作用 |
getEntries() | 返回一个列表,该列表包含一些用于承载各种性能数据的对象,不做任何过滤 |
getEntriesByType() | 返回一个列表,该列表包含一些用于承载各种性能数据的对象,按类型过滤 |
getEntriesByName() | 返回一个列表,,该列表包含一些用于承载各种性能数据的对象,按名称过滤 |
observer.observe(...)
方法接受可以观察到的有效的入口类型。这些输入类型可能属于各种性能API,比如User timing
或Navigation Timing API
。有效的entryType
值:
属性 | 别名 | 类型 | 描述 |
frame navigation |
PerformanceFrameTiming PerformanceNavigationTiming |
URL | 文件的地址 |
resource | PerformanceResourceTiming | URL | 所请求资源的解析URL |
mark | PerformanceMark | DOMString | 通过调用创建标记时使用的名称performance.mark() |
measure | PerformanceMeasure | DOMString | 通过调用创建度量时使用的名称performance.measure() |
paint | PerformancePaintTiming | DOMString | 无论是'first-paint'或'first-contentful-paint' |
longtask | PerformanceLongTaskTiming | DOMString | 报告长任务的实例 |
静态资源监控示例代码
function filterTime(a, b) { return (a > 0 && b > 0 && (a - b) >= 0) ? (a - b) : undefined; } let resolvePerformanceTiming = (timing) => { let o = { initiatorType: timing.initiatorType, name: timing.name, duration: parseInt(timing.duration), redirect: filterTime(timing.redirectEnd, timing.redirectStart), // 重定向 dns: filterTime(timing.domainLookupEnd, timing.domainLookupStart), // DNS解析 connect: filterTime(timing.connectEnd, timing.connectStart), // TCP建连 network: filterTime(timing.connectEnd, timing.startTime), // 网络总耗时 send: filterTime(timing.responseStart, timing.requestStart), // 发送开始到接受第一个返回 receive: filterTime(timing.responseEnd, timing.responseStart), // 接收总时间 request: filterTime(timing.responseEnd, timing.requestStart), // 总时间 ttfb: filterTime(timing.responseStart, timing.requestStart), // 首字节时间 }; return o; }; let resolveEntries = (entries) => entries.map(item => resolvePerformanceTiming(item)); let resources = { init: (cb) => { let performance = window.performance || window.mozPerformance || window.msPerformance || window.webkitPerformance; if (!performance || !performance.getEntries) { return void 0; } if (window.PerformanceObserver) { let observer = new window.PerformanceObserver((list) => { try { let entries = list.getEntries(); cb(resolveEntries(entries)); } catch (e) { console.error(e); } }); observer.observe({ entryTypes: ['resource'] }) } else { window.addEventListener('load', () => { let entries = performance.getEntriesByType('resource'); cb(resolveEntries(entries)); }); } }, };