Observer
网页开发中经常会和用户交互而使用一些监听事件(例如onclick,onchange等).如果对于一些用户不直接触发的元素(例如渐变等),那就需要使用Observer去监听
- 浏览器为我们提供了五种
Observer
(观察者)来监听这些变动:MutationObserver
,IntersectionObserver
,PerformanceObserver
,ResizeObserver
,ReportingObserver
- 以下观察者api都是构造函数
- 观察者属于微任务,并且优先级小于Promise
IntersectionObserver
IntersectionObserver
(交叉观察者)用于观察一个元素是否在视窗可见.构造函数创建并返回一个新的IntersectionObserver
对象
如果未指定或为空字符串,则缺省的值为属性的默认值
一般用于无限滚动,图片懒加载,埋点,控制动画/视频执行
- 无论是使用视口(body)还是其他元素作为根,API 的工作方式都相同,并且会异步查询观察目标元素的可见性发生变化,就会执行提供的回调函数
- 通过提供一种新方法来异步查询元素相对于其他元素或全局视口的位置
- 异步处理消除了昂贵的
DOM
和样式查询,连续轮询以及使用自定义插件的需求
- Intersection Observer的三个步骤
- 创建观察者
- 定义回调事件
- 定义要观察的目标对象
实例方法
IntersectionObserver.observe(target)
:告诉要观察的目标元素
IntersectionObserver.takeRecords()
:从IntersectionObserver的通知队列中删除所有待处理的通知,并将它们返回到IntersectionObserver对象的新Array对象中
IntersectionObserver.unobserve()
指定停止观察特定目标元素
IntersectionObserver.disconnect()
:停止IntersectionObserver对象观察任何目标
创建观察者
- 接收一个回调函数.只要目标元素发生变化就会触发回调函数
- 第二个参数是一个可选项
let options = {
root: document.querySelector('#scrollArea'),
rootMargin: '0px',
//阈值为1.0表示当100%的目标在选项指定的元素中可见时,将调用回调
//每个阈值是观测目标的交集区域与边界框区域的比率
threshold: 1.0
}
let observer = new IntersectionObserver(callback, options?);
option字段
root
:用作检查目标可见性的视口的元素.必须是目标的祖先.如果未指定或缺省为浏览器视口(html)
rootMargin
根周围的边距(默认全部为0).语法类似于margin
可以是百分比或者像素,用于在计算交集之前增大或缩小根元素边界框的矩形偏移量,有效的扩大或者缩小根的判定范围从而满足计算要求.(top,right,bottom,left)
threshold
:阈值.单个数字或数字数组.默认值为 0(这意味着只要有一个像素可见,就会运行回调)阈值是监听对象的交叉区域和边界区域的比例,每当监听对象超过阈值就会触发回调
- 如果只想检测可见性何时超过 50% 标记,则可以使用值 0.5.
- 如果希望每次可见性每次超过 25% 时都运行回调,则应指定数组 [0, 0.25, 0.5, 0.75, 1]
- 如果值为 1.0 表示在每个像素可见之前,不会认为阈值已通过.
监听观察的目标对象
开启对目标对象的监听,如果没有
const target = document.querySelector(".target");
observer.observe(target);
回调函数
callback是添加监听后,当监听目标发生滚动变化时触发的回调函数.
- 第一个参数
entries
(数组),即IntersectionObserverEntry
实例.描述了目标元素与root的交叉状态.
| |
boundingClientRect |
返回包含目标元素的边界信息,返回结果与element.getBoundingClientRect() 相同 |
intersectionRatio |
返回目标元素出现在可视区的比例 |
intersectionRect |
用来描述root和目标元素的相交区域 |
isIntersecting |
返回一个布尔值,下列两种操作均会触发回调:1.如果目标元素出现在root可视区,返回true.2. 如果从root可视区消失,返回false |
rootBounds |
用来描述交叉区域观察者(intersection observer)中的根. |
target |
目标元素:与根出现相交区域改变的元素 (Element) |
time |
返回一个记录从 IntersectionObserver 的时间原点到交叉被触发的时间的时间戳 |
- 第二个参数就是
IntersectionObserver
这个实例对象本身.可以使用实例上的方法.
图片懒加载
<body>
<img width="200px" height="200px" src="logo.png"
data-src="0.jpg">
<img width="200px" height="200px" src="logo.png"
data-src="1.jpg">
<img width="200px" height="200px" src="logo.png"
data-src="2.jpg">
</body>
<script>
const img = document.getElementsByTagName('img');
let observe = new IntersectionObserver((entries, observe) => {
entries.forEach(item => {
if (item.isIntersecting) {
item.target.src = item.target.dataset.src
observe.unobserve(item.target)
}
})
}, { rootMargin: "0px 600px 0px -600px" })
// observe遍历监听所有img节点
Array.from(img).forEach(img => observe.observe(img))
</script>
先说一下dataset
属性,可以在很多网站中看到这个data-为前缀的属性
- dataset:浏览器很早就支持以键(data)-值命名的自定义属性了
- 设置属性:
Element.dataset.dart="dark"
.例如上图代码(item.target.dataset.dart="dark"
)在挂载到标签时,会自动加上data
这个键.就像这样data-dart="dark"
- 获取属性:
Element.dataset.src
,不需要加上data-
前缀
- 删除属性:
delete Element.dataset.src
- 当然我们完全可以使用
getAttribute
等属性来进行自定义操作
HTMLCollectionOf<>
和NodeListOf<>
的区别
- 参考:DOM 标准 (whatwg.org)
- 由于是历史遗留的产物,
HTMLCollectionOf
他返回的是一个集合,并不支持任何数组的高级api
- 并且一切由
getElements...
返回的节点都是动态的集合类型,没有实现forEach等方法
- 动态的:如果基本的文档改变时.所有
HTMLCollection
对象会立即改变
NodeListOf
是静态的.实现了所有的高级数组都有的api,forEach
等
- 了解了这些,使用元素选择的时候也可以使用
querySelectAll()
来选择元素.他会返回一个NodeListOf的类型
理解可视区
- 重要的一点就是可视区的理解
- intersectionRatio对应的是
threshold
- isIntersecting对应的是
rootMargin
- 只要理解了
rootMargin
就很容易理解threshold的概念
- 理解margin,由于文档流的缘故,在设置margin的
top
或者bottom
任意值的时候会移动盒子.如果设置left
或者right
必须同时设置才会改变盒子原来的位置,只设置一个值只会撑大盒子
- 例如上面图中设置的整体元素会向左移动600px.并且目标元素是相对于视口来说,但是理论上所有的图片都应该移动到视口之外的位置.并且不可以看到图片的懒加载.但是由于浏览器本身有一定的默认值,我们会得到最后一个图片是触发观察者实现懒加载的
- 明白了这个,就可以明白
threshold
,只有目标元素的可见性达到视口的一定比例(threshold的属性值)之后才可以触发观察者模式
MutationObserver
Mutation Observer
是异步触发,DOM的变动并不会马上触发,而是要等到当前所有DOM`操作都结束才触发
- 可以通过配置项,监听目标DOM下子元素的变更记录
- 构造函数返回一个新的,包含监听 DOM 变化回调函数的
MutationObserver
对象
- 使用用途
- 一般用于更高性能的数据绑定及响应
- 实现视觉差滚动
- 图片预加载
- 实现富文本编辑器
Mutation实例方法
MutationObserver.observe(dom,options)
:阻止MutationObserver
实例继续接收的通知,直到再次调用其observe()
方法,该观察者对象包含的回调函数都不会再被调用
MutationObserver.takeRecords()
:从MutationObserver的通知队列中删除所有待处理的通知,并将它们返回到MutationRecord
(保存每次的变化信息)对象的新Array
MutationObserver.disconnect()
:停止MutationObserver对象观察任何目标
Mutation创建观察者和监听目标对象
const MutationObserver = new MutationObserver(callback)
MutationObserver.observe(dom, options)
- 第一个参数是一个dom对象,被观察子节点(目标元素)的父节点
- 第二个参数options是一个MutationObserverInit | MDN (mozilla.org)对象
attributeFilter
:(无默认值)要监视的特定属性名称的数组(如['src','class']
).如果未包含此属性,则对所有属性的更改都会触发变动通知
attributes
:默认值false.设置true观察受监视元素的属性值变更
characterData
:无默认值.设为true监视指定目标节点或者子节点树中节点所包含的字符数据的变化
characterDataOldValue
:无默认值.设为true,是否观察文本的内容(文本节点)
childList
:默认值为false.设为true,监视木匾检点添加或删除新的子节点(不包括修改子节点以及子节点后代的变化).如果subtree
为true,则包含子节点
subtree
:默认值为false,设置true,将监视范围扩展到目标节点以及子节点
- 属性特定项
- 其中调用
observe()
方法时childList
,attributes
,characterData
或者attributeOldValue
,characterDataOldValue
两组中,至少有一个必须为 true
,否则会抛出异常
attributeFilter/attributeOldValue
> attributes
characterDataOldValue
> characterData
- 避免重复的特定项,不需要同时设置同样的效果
mutation回调函数
同样是接收两个参数
- 第一个参数是
MutationRecords
,依然是一个数组.其中每个MutationRecord
对象,记录着DOM
每次发生变化的变动记录.MutationRecord
对象包含了DOM的相关信息
属性 | 描述 |
target |
被修改影响的目标 dom 节点 |
type |
变化的类型,也就是MutationObserverInit对象中的三种`attributes`,`characterData`或`childList`,并且返回该类型 |
attributeName |
针对`attributes`类型的变化时,返回被修改属性的名字(或者null) |
attributeNamespace |
**针对命名空间的`attributes`类型的变化.返回被修改属性的命名空间,或者null |
oldValue |
如果在`MutationObserverInit`对象中启用(`attributeOldValue`或`characterDataOldValue`为true).则`attributes`或`characterData`的变化事件会返回变化之值或数据.`childList`类型的变化始终将这个属性设置为null |
addedNodes |
针对`childList`的变化,返回包含变化中添加节点的`NodeList`,没有节点被添加,返回空`NodeList`数组 |
previousSibling |
对于`childList`变化.返回被添加或移除的节点之前的兄弟节点,或者null |
nextSibling |
对于`childList`变化,返回被添加或移除的节点之后的兄弟节点.或者null |
removedNodes |
对于`childList`变化,返回被移除的节点(没有则为null) |
- 第二个参数就是
MutationObserver
这个实例对象本身.可以使用实例上的方法.
MutationObserver的引用
MutationObserver
对要观察的目标节点的引用属于弱引用,所以不会妨碍垃圾回收程序回收目标节点
- 目标节点对于
MutationObserver
是强引用.如果目标节点从DOM中被移除,随后被垃圾回收,则关联的 MutationObserver 也会被垃圾回收
MutationRecord
的引用
MutationRecord
实例至少包含对已有DOM节点的一个引用,即里面的target属性,如果变化是childList
类型,则会包含多个节点的引用
- 记录队列和回调处理的默认行为是耗尽这个队列,处理每个
MutationRecord
,然后让它们超出作用域并被垃圾回收
MutationObserver
核心是异步回调与记录队列模型.为了在大量变化事件发生时不影响性能,每次变化的信息由oberver实例决定.保存在MutationRecord实例中,然后添加到记录队列
- 记录队列对每个 MutationObserver 实例都是唯一的,是所有 DOM 变化事件的有序列表(DOM变化事件都会以数组的形式存在MutationRecord中),多次修改的信息会在一次回调中执行
- 有时候需要保存某个观察者的完整变化记录,那么就保存所有的
MutationRecord
实例,也就会保存它们引用的节点,而这会妨碍这些节点被回收
- 如果需要尽快地释放内存,可以从每个
MutationRecord
中抽取出最有用的信息,保存到一个新对象,然后释放MutationRecord
中的引用
ResizeObserver(尚在开发)
ResizeObserver
构造器创新一个新的ResizeObserver
对象,用于接收Element
内容区域的改变或SVGElement
的边界框改变改变
- 用途:更智能的响应式布局(取代@media)以及响应式组件
- 由于resize事件会监听视窗的变化而不是元素的大小发生变化.可能一秒内会触发几十次,导致性能问题
Resize实例方法
observe(target,options?)
:用于指定观察一个指定的Element
或者SVGElement
disconnect()
:停止和取消目标对象上所有对Element
或者SVGElement
监视
unobserve()
:用于结束一个指定的Element
或者SVGElement
监视
创建Resize实例
const ResizeObserver = new ResizeObserver(callback)
resizeObserver.observe(target,options?);
- options是一个指定观察设置的可选参数对象.目前只有一个可设置的选项
box
:设置观察者将以哪种盒子模型来观察变动
- 可以设置为
content-box
(默认值),border-box
或者device-pixel-content-box
Resize回调函数
PerformanceObserver
PerformanceObserver
用于监测性能度量事件,在浏览器的性能时间轴记录下一个新的 performance entries 的时候将会被通知
- 用途:更细颗粒的性能监控.分析新跟那个对业务的影响(交互快/慢是否会影响销量)
- 尽可能使用
PerformanceObserver
,而不是通过Performance
获取性能参数及指标
- 避免不知道性能事件啥时候会发生,需要重复轮训
timeline
获取记录。
- 避免产生重复的逻辑去获取不同的性能数据指标
- 避免其他资源需要操作浏览器性能缓冲区时产生竞态关系
实例方法和MutationsObserver一样,但是observe()
只接受options
创建Performance实例
const PerformanceObserver = new PerformanceObserver(callback)
PerformanceObserver.observe({entryTypes: ["measure"]})
options
只接收一个entryTypes的键,值为一个性能检测数组
属性 | 别名 | 类型 | 描述 |
`frame`, `navigation` |
PerformanceFrameTiming, PerformanceNavigationTiming |
URL |
文件的地址 |
`resource` |
PerformanceResourceTiming |
URL |
文件请求资源解析的URL.只有在资源加载完毕后才会创建 |
`mark` |
PerformanceMark |
DOMString |
通过调用创建标记使用的名称.会在资源获取开始时创建(`performance.mark(name)`) |
`measure` |
PerformanceMeasure |
DOMString |
通过调用创建度量时使用的名称.会在对资源操作时创建(`performance.measure(name)`) |
`paint` |
PerformancePaintTiming |
DOMString |
渲染时间点的信息接口.找出那些花费太多时间去绘制的区域 |
Performance回调函数
回调函数只接受一个参数,该参数是PerformanceObserverEntryList
对象.该对象有三个接口
getEntries()
:返回所有的PerformanceEntry
对象组成的数组
getEntriesByType(entryType)
:返回指定的entryType
类型组合成的PerformanceEntry
对象数组
getEntriesByName(name)
:返回通过指定的属性名(例如performance.measure(name)
的name)组合成PerformanceEntry
对象的数组
PerformanceEntry
对象
- 属性值:
name
:该性能条目的名字.例如mark,measure通过指定名称name
entryType
:上述的options包含所有的entryType属性
startTime
:返回PorformanceEntry
的第一个时间戳
frame
:当页面开始加载时,返回的时间戳
mark
:当使用performance.mark(name)
创建mark标记之后返回的时间戳
measure
:当使用performance.measure(name)
创建measure标记之后返回的时间戳
navigation
:返回值为0的时间戳
resource
:返回浏览器开始获取资源的时间戳
duration
:该资源的耗时时间
- 方法:
toJSON()
:返回 JSON 格式数据的PerformanceEntry对象
<body>
<button onclick="measureClick()">Measure</button>
<img src="http://zyjcould.ltd/blog/%E6%B5%8F%E8%A7%88%E5%99%A8%E8%A7%86%E5%8F%A3.png" />
<script>
const performanceObserver = new PerformanceObserver(list => {
list.getEntries().forEach(entry => {
console.log(entry.entryType);
console.log(entry.startTime);
console.log(entry.name);
console.log(entry.duration);
console.log(entry.toJSON());
})
});
performanceObserver.observe({ entryTypes: ['resource', 'mark', 'measure'] });
performance.mark('registered-observer');
function measureClick() {
performance.measure('button clicked');
}
</script>
</body>
ReportingObserver(实验)
ReportingObserver()
构造函数会创建一个新的 ReportingObserver
对象实,该实例可用于收集和获取reports
- 使用:将浏览器弃用的
API
或者运行时浏览器的干预行为由自己约束
- 违反浏览器的选项时
- JS的异常和错误(替代
window.onerror
)
- 未处理的promise的
reject
(替代window.onunhandledrejection
)
ReportingObserver实例方法和MutationObserver
的实例方法一样.但是observer()
不需要任何参数
创建Reporting实例
let options = {
types: ['deprecation'],
buffered: true
}
const reportingObserver=new ReportingObserver(callback, options?)
- options提供两个属性,
types
和buffered
types
:提供三个属性值
deprecation
:浏览器运行时遇到弃用的api会打印这个选项
intervention
:浏览器自己的干预行为.可能遇到一些不安全的行为(如带有不安全的iframe,过时的api等)
crash
:浏览器崩溃时的行为
buffered
:布尔值,如果时true,可以查看创建观察者之前生成的报告(使用于延迟加载的情况,不会错过页面加载之前发生的事情)
Reporting回调函数
提供两个参数,第一个参数是一个reports
数组对象.同样也可以通过takeRecords()
实例方法获取这些数组
report
对象有三个属性:body
,type
,url
type
:返回的是report类型,即options选项中的types
url
:返回的是生成report的文档
body
:返回report正文,包含详细的report对象,目前只有两种body对象(却决于type的返回值)
DeprecationReportBody、InterventionReportBody
id
:已弃用的功能或 API 的字符串
anticipatedRemoval
:Data对象,表示应从浏览器中要删除的日期.如果日期位置,返回null
message
:字符串,api的弃用说明.包括新功能的取代说明
sourceFile
:string类型,使用已弃用api的源文件路径.已知或其他返回null
lineNumber
:number类型,表示源文件中使用已弃用的功能的行.已知或其他返回null
columnNumber
:number类型,表示源文件中使用已弃用的功能的行=列.已知或其他返回null
CrashReportBody
reason
:表示崩溃原因的字符串.如果返回的是oom
:浏览器内存不足.如果返回的是unresponsive
:页面由于无响应而被终止