快速学会 Observer API

1,612 阅读9分钟

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

本文将带你快速学习 Observer API,对应有 4 个 API:

  • Intersection Observer API:观察可见性
  • Resize Observer API:观察大小变化
  • Mutation Observer API:提供了监视 DOM 树的更改的能力
  • Performance Observer API:用于观察性能

IntersectionObserver

此 API 提供了一种异步观察目标元素与祖先元素或顶级文档视图交叉处变化的方法,简单的来说就是可以检测子元素在父元素中的可见性。

通常我们会在图片懒加载、内容无限滚动加载等功能上使用到它。

使用方法

IntersectionObserver API 使用方法如下:

  1. 创建 observer 对象,第一个参数为回调函数,第二个参数为观察器的配置。
const options = {
  root: document.querySelector('#scrollArea'),
  rootMargin: '0px',
  threshold: 1.0
}
const callback = (entries, observer) => {}

let observer = new IntersectionObserver(callback, options)
  1. 观察对象
observer.observe(el)
  1. 取消观察
observer.unobserve(el)

API 介绍

IntersectionObserver

构造函数:callback 为回调函数,回调函数第一个参数为 IntersectionObserverEntry 数组,第二个参数为自身 observer 对象。构造函数第二个参数可选,包括三个属性值:root, rootMargin, thresholds;不设置的情况下会有默认值。

回调函数在一开始会调用一次,IntersectionObserverEntry 数组包括所有被观察目标,后续只有在被观察目标出现在可视区或者离开可视区后才会被调用,同时 IntersectionObserverEntry 数组只包括发生变化的那些被观察目标

new IntersectionObserver(callback[, options])

属性:

  • root:根节点,所有被观察的元素都必须是根节点的子元素,默认是文档可视区域
  • rootMargin:根节点边距,可以用于增加或者减小根节点的判断区域,值和 CSS margin 类似,默认是 0px
  • thresholds:取值范围为 [0, 1], 指定子元素和根元素交叉比率为多少时视才为可见,默认是 0,即子元素盒子和根元素的盒子相邻。也可以设置为数组,为数组就时在交叉比率为每个数组项比率时都会触发回调

方法:

  • disconnect:停止所有元素的观察
  • observe:观察一个元素
  • takeRecords:返回一个当前观察的目标的数组,数组项类型为 IntersectionObserverEntry
  • unobserve:取消某个元素的观察

IntersectionObserverEntry

该接口描述了目标元素与其根容器的交集信息,IntersectionObserver 构造函数中回调函数的第一个参数就是此类型的数组。

属性:

  • boundingClientRect:该 DOM 元素的盒子信息
  • intersectionRatio:交集的比率
  • intersectionRect:交集的盒子信息
  • isIntersecting:是否有交集
  • rootBounds:根元素的盒子信息
  • target:DOM 元素
  • time:发生交集的时间,这个时间是相对于 observer 创建的时间

示例

我们直接来看示例,在示例中将演示 rootMargin 和 thresholds 参数的效果

<div class="container">
  <div class="item">Item 1</div>
  <div class="item">Item 2</div>
  <div class="item">Item 3</div>
  <div class="item">Item 4</div>
  <div class="item">Item 5</div>
  <div class="item">Item 6</div>
  <div class="item">Item 7</div>
  <div class="item">Item 8</div>
  <div class="item">Item 9</div>
</div>

简单地写了个 HTML: 一个容器,里面有 9 个子项,容器高度不够显示所有子项时,支持滚动查看。

const options = {
  root: document.querySelector('.container'),
  rootMargin: "0px",
  threshold: 0
};

const observer = new IntersectionObserver((entries) => {
  console.log(entries)
  for(let entry of entries) {
    if (entry.isIntersecting) {
      console.log('load: ', entry.target.innerHTML)
    }
  }
}, options);
 
for (let item of document.querySelectorAll('.item')) {
  observer.observe(item)
}

代码地址:codepen.io/wuzhengyan2…

JS 这边按照上面使用方法的步骤,创建完 observer 对象后,观察所有的 item 项。根据 isIntersecting 判断是否有交集,有就控制台输出对应的信息。

rootMargin 和 thresholds 默认参数的情况,即 rootMargin 为 0, thresholds 为 0:

observer-1.gif

我们可以看到一开始前四个子项是判断为有交集(即可见的),在滚动视图时,可以看到后续的子项在自己盒子区域刚出现在可视区时,就判断为有交集。这就是 thresholds 为 0 的效果。

rootMargin 为默认参数的情况,thresholds 为 1 的情况:

observer-2.gif

可以看到只有在子项完全出现在根元素可视区的时候,才会判断为可见。

rootMargin 为 64(子项高度加边距),thresholds 为 1 的情况:

observer-3.gif

可以和第二种情况对比,一开始第五个子项就被视为可见,后续滚动和第二种情况对比也是在子项出现在边缘时就被视为可见。原因时设置了 rootMargin,相当与扩大了根元素的可视区判断区域。

小结

IntersectionObserver API 的使用并不难,可以在上面代码地址中多体验下,就可以很快熟悉。

ResizeObserver

Resize Observer API 提供了一种性能机制,通过这种机制,代码可以监视元素大小的变化,并在每次大小变化时触发回调。

通常页面需要做一个响应式处理时,我们可以使用到它。

使用方法

IntersectionObserver API 使用方法如下:

  1. 创建 observer 对象,只有一个参数为回调函数。
const callback = (entries, observer) => {}

let observer = new IntersectionObserver(callback)
  1. 观察对象
observer.observe(el)
  1. 取消观察
observer.unobserve(el)

API 介绍

ResizeObserver

构造函数:callback 为回调函数,回调函数第一个参数为 ResizeObserverEntry 数组,第二个参数为自身 observer 对象。同样的回调函数在一开始会调用一次

new ResizeObserver(callback)

方法:

  • disconnect:停止所有元素的观察
  • observe:观察一个元素
  • unobserve:取消某个元素的观察

ResizeObserverEntry

该接口描述了新元素的大小信息,ResizeObserverEntry 构造函数中回调函数的第一个参数就是此类型的数组。

属性:

  • borderBoxSize:一个数组,数组中每个对象都有 blockSize 和 inlineSize,水平书写模式下,inlineSize 就是宽度,blockSize 就是高度,竖直模式则相反
  • contentBoxSize:同 borderBoxSize,不过宽高不包括内边距和边框
  • devicePixelContentBoxSize:设备像素为单位新内容框大小
  • contentRect:DOMRectReadOnly 类型对象,兼容性比上面的好,建议使用这个对象

示例

<div class="container">
  <div class="img"></div>
  <div class="info">Hello World</div>
</div>
const resizeObserver = new ResizeObserver(entries => {
  for (let entry of entries) {
    console.log(entry, entry.borderBoxSize )
    if(entry.contentRect.width > 800) {
      entry.target.style.fontSize = '36px'
    } else {
      entry.target.style.fontSize = '24px'
    }
  }
  console.log('Size changed');
});

resizeObserver.observe(document.querySelector('.info'))

这个例子中使用了 flex 布局,左边的区域固定大小,右边的自适应,在右边宽度小于 800 时,字体样式也会对应缩小。

observer-4.gif

小结

ResizeObserver 的出现,让我们在做响应式处理时,有了更多的选择。

MutationObserver

MutationObserver 接口提供了监视 DOM 树的更改的能力。

使用方法

  1. 创建 observer 对象,第一个参数为回调函数。
const callback = (mutationsList, observer) => {}

let observer = new MutationObserver(callback)
  1. 观察对象,第一个参数为 DOM 对象,第二个为配置
const config = { attributes: true, childList: true, subtree: true }
observer.observe(el, config)
  1. 取消观察
observer.disconnect()

API 介绍

MutationObserver

构造函数:callback 为回调函数,回调函数第一个参数为 MutationRecord 数组,第二个参数为自身 observer 对象。

new MutationObserver(callback)

方法:

  • disconnect:停止所有元素的观察
  • observe:观察一个元素, 第一个参数的 DOM 节点,第二个参数时配置项,配置项来自 MutationObserverInit 字典
  • takeRecords:返回 MutationRecord 数组,同时删除在通知队列中的通知。

MutationObserverInit

observe 方法的配置项字典

  • subtree:设置为 true 以将监视扩展到位于目标节点的整个子树,默认为 false
  • childList:监视子节点的新增或者删除,默认为 false
  • attributes:监视属性值的更改,默认为 false,如果指定了 attributeFilter 和 attributeOldValue 就默认为 true
  • attributeFilter:指定要监视的属性的数组
  • attributeOldValue:设置为 true,就会在变更时记录旧值,默认为 false
  • characterData:监视文本的修改:默认为 false
  • characterDataOldValue:设置为 true,就会在变更时记录旧的文本值,默认为 false

MutationRecord

  • type:变化的类型,值为 attributes | characterData | childList
  • target:发生变化的 DOM 元素,属性变化就指向属性变化的 DOM,文本变化就指向文本节点,子元素新增或删除就指向子元素的父节点
  • addedNodes:新增的节点数组
  • removeNodes:删除的节点数组
  • previousSibling:返回新增或删除节点的前一个节点
  • nextSibling:返回新增或删除节点的下一个节点
  • attributeName:变化的属性名
  • attributeNamespace:变化的属性命名空间
  • oldValue:旧值,在开启 attributeOldValue 后才有值

示例

<div class="container">
  <div>Item1</div>
  <div>Item2</div>
  <div>Item3</div>
</div>
const targetNode = document.querySelector('.container');
const observerOptions = {
  childList: true,
  attributes: true,
  subtree: true,
  characterData: true,
  attributeOldValue: true
}

function callback(mutationList, observer) {
  console.log(mutationList)
}

const observer = new MutationObserver(callback);
observer.observe(targetNode, observerOptions);

代码地址:codepen.io/wuzhengyan2…

这个例子也很简单,查看下各个配置项属性的效果和 MutationRecord 的各个字段

observer-5.gif

上述演示了属性变化,子元素新增删除,还有文本变化,可以看到对应的输出都和 API 文档一致

小结

MutationObserver 可以在以下场景中使用:

  • 通知用户当前所在的页面所发生的一些变化
  • 根据 DOM 的变化来动态加载 JavaScript 模块

最后注意 MutationObserver 的回调触发是异步的,在 DOM 发生变化后,不会同步回调

PerformanceObserver

PerformanceObserver 接口用于观察性能,这个大部分情况下我们不会使用到,可以先简单了解下它的能力,后续使用时在查看。

使用方法

  1. 创建 observer 对象,第一个参数为回调函数。
const callback = (list, observer) => {}

let observer = new PerformanceObserver(callback)
  1. 指定要观察的性能项
observer.observe({ entryTypes: ["measure"] })
  1. 取消观察
observer.disconnect()

API 介绍

PerformanceObserver

构造函数:callback 为回调函数,回调函数第一个参数为 PerformanceObserverEntryList 对象

new PerformanceObserver(callback)

属性:

  • supportedEntryTypes: 返回支持的条目类型

方法:

  • disconnect:停止所有观察
  • observe:观察一个性能项,参数为 options:entryTypes 指定一个性能类型数组,type:指定性能类型,不能同时和 entryTypes 一起使用,buffer 指明相关信息是否要进入缓存区,只能时 type 一起使用
  • takeRecords:返回性能条目数组

其他 API 这里不展示介绍,有兴趣的可以去 MDN 上查看,如 performance entries

示例

例子可以去 codepen 上查看,比如 codepen.io/_tianxia/pe…, codepen.io/manojs77/pe…

小结

PerformanceObserver 是用于观察性能,比如代码执行时间,资源加载时间等等。在使用它之前,需要对 performance api 先做个了解。

兼容性

兼容性方法可以看下图,除了 IE 外,其他兼容性还可以。其他 Observer API 和下图类似。

image.png

总结

4 个 Observer API 都有类似的使用方式,但是各自使用场景不一样。记住这些 API 的能力,在需要的时候可以在去查看文档。