浏览器监听DOM变化的几个常用Observer Api

260 阅读5分钟

平时处理真实DOM的一些交互的时候,经常需要去监听某个DOM元素的位置、尺寸的变化而去做一些额外的处理工作,往往css用的不够出色到情况下,使用js去监听变化可以也能达到对应的效果。 例如:

  1. 监听滚动条做图片的懒加载
  2. 根据浏览器的可视区域的变化(用户放大缩小、拖拽等...)而自动响应某个区域的元素的尺寸变化,例如最经常遇到的是表格高度同步问题
  3. 富文本控件可输入区域可随着当前可视区域的大小进行最大高度限制
  4. 水印处理,往往内容水印的处理方式是在当前可视区域上增加一层dom创建的水印内容,为保证水印不被删除,会通过监听当前水印父类中水印元素是否被删除而进行再次填充。

ResizeObserver

ResizeObserver 是浏览器提供的一个用于监听 DOM 元素尺寸变化的 API,属于浏览器的异步观察器接口。它能够在元素的内容区域或边框盒区域大小发生变化时,触发相应的回调函数,相较于传统通过定时器轮询获取元素尺寸或监听 window.resize 事件来判断元素尺寸变化的方式,ResizeObserver 更加高效,不会造成不必要的性能损耗,因为它只会在实际发生尺寸变化时才执行回调,而不是频繁地检查。

const observer = new ResizeObserver(callback);

callback 是一个回调函数,当被观察的 DOM 元素尺寸发生变化时,该函数会被调用。回调函数接收一个 entries 参数,它是一个 ResizeObserverEntry 对象的数组,每个 ResizeObserverEntry 对象都描述了一个被观察元素的尺寸变化信息。

ResizeObserverEntry

ResizeObserverEntry对象包含了被观察元素尺寸变化的相关信息,其主要属性如下:

  1. target 类型为 Element,表示被观察的 DOM 元素。法contentRect:类型为 DOMRectReadOnly,是一个只读的矩形对象,包含了被观察元素内容区域的尺寸和位置信息,属性包括 x(内容区域左上角的 x 坐标)、y(内容区域左上角的 y 坐标)、width(内容区域的宽度)、height(内容区域的高度)、top(内容区域顶部相对于视口的 y 坐标)、right(内容区域右侧相对于视口的 x 坐标)、bottom(内容区域底部相对于视口的 y 坐标)、left(内容区域左侧相对于视口的 x 坐标)。

  2. borderBoxSize:该属性是一个数组,数组中的每个元素都是一个 ResizeObserverSize 对象,描述了元素边框盒的尺寸。在某些情况下,元素可能有多个边框盒(例如,当元素使用了 box-decoration-break 属性时),此时数组会包含多个 ResizeObserverSize 对象。ResizeObserverSize 对象包含 inlineSize(内联轴方向的尺寸,在水平书写模式下通常是宽度)和 blockSize(块联轴方向的尺寸,在水平书写模式下通常是高度)属性 。​

  3. contentBoxSize:与 borderBoxSize 类似,也是一个数组,每个元素是 ResizeObserverSize 对象,描述了元素内容盒的尺寸。

const targetElement = document.getElementById('my-element');​

const observer = new ResizeObserver((entries) => {​

	for (const entry of entries) {​
	
	console.log(`元素 ${[entry.target.id](http://entry.target.id/)} 的新宽度为${entry.contentRect.width},新高度为 ${entry.contentRect.height}`);​
	}​

});​
// 开启监听
observer.observe(targetElement);
// 关闭监听
// observe.unobserve(targetElement);
observe(target)

该方法用于开始观察指定的 DOM 元素 target。一旦调用此方法,当 target 的尺寸发生变化时,在构造函数中传入的回调函数就会被触发

unobserve(target)

用于停止观察指定的 DOM 元素 target。调用此方法后,即使 target 的尺寸发生变化,观察器的回调函数也不会再因 target 的变化而被触发

disconnect()
observer.disconnect();

该方法用于停止观察所有已被观察的 DOM 元素。调用此方法后,观察器将不再对任何元素的尺寸变化做出响应,请注意,只有调用该函数才会销毁ovserver,不然会一直存在。

IntersectionObserver

IntersectionObserver 是浏览器提供的一种异步观察器接口,用于异步观察目标元素与祖先元素或视口(viewport)之间的交叉状态变化。它允许开发者在目标元素进入或离开另一个元素(称为根元素,通常是视口)的可见区域时,执行特定的代码逻辑。与传统通过定时器轮询检查元素可见性的方式相比,IntersectionObserver 更加高效,因为它仅在交叉状态实际发生变化时才触发回调,减少了不必要的性能开销。

const observer = new IntersectionObserver(callback[, options]);
  • callback:是一个回调函数,当被观察的目标元素与根元素的交叉状态发生变化时,该函数会被调用。回调函数接收两个参数:​

  • entries:是一个 IntersectionObserverEntry 对象的数组,每个对象描述了一个目标元素的交叉状态变化信息。

  • options: (可选):是一个配置对象,用于设置观察器的行为,包含以下属性:

      • root:指定根元素,即目标元素要与之比较交叉状态的祖先元素。如果未指定或设置为 null,则默认使用视口作为根元素。例如:document.getElementById('parent-element'), 一般不用,可以忽略。​
    • rootMargin:字符串类型,用于设置根元素的外边距,类似于 CSS 中的 margin 属性。它会在根元素周围创建一个 “扩展” 或 “收缩” 的区域,影响交叉状态的计算。值的格式与 CSS 边距值相同,如 '10px 20px 30px 40px'(上、右、下、左),默认值为 '0px 0px 0px 0px', 一般可以忽略。

    • threshold:可以是一个数字或数字数组。用于指定交叉比例的阈值,当目标元素与根元素的交叉比例达到这些阈值时,会触发回调函数。例如,0.5 表示当目标元素有 50% 进入根元素可见区域时触发回调;[0, 0.25, 0.5, 0.75, 1] 则表示在目标元素开始进入、进入 25%、50%、75%、完全进入根元素可见区域时,都会触发回调,默认值为 [0],这个选项比较重要,可以根据可视区域的某个状态进行过滤提示。

  • IntersectionObserverEntry 对象包含了目标元素交叉状态变化的相关信息,其主要属性如下:​

    • time:类型为 DOMHighResTimeStamp,表示交叉状态变化发生的时间,是一个高精度时间戳。​

    • rootBounds:类型为 DOMRectReadOnly,是一个只读的矩形对象,描述了根元素的尺寸和位置信息,包含 x、y、width、height、top、right、bottom、left 等属性。如果 root 设置为 null,则该属性表示视口的边界。​

    • boundingClientRect:类型为 DOMRectReadOnly,同样是一个只读的矩形对象,描述了目标元素在视口中的尺寸和位置信息。​

    • intersectionRect:类型为 DOMRectReadOnly,描述了目标元素与根元素交叉部分的尺寸和位置信息。​

    • intersectionRatio:类型为 number,表示目标元素与根元素的交叉比例,取值范围为 0 到 1。0 表示完全不交叉,1 表示目标元素完全在根元素可见区域内。​

    • isIntersecting:类型为 boolean,表示目标元素当前是否与根元素交叉,true 表示正在交叉,false 表示不交叉。​

    • target:类型为 Element,表示被观察的目标 DOM 元素。​

    const targetElement = document.getElementById('my-target');​
    
    const observer = new IntersectionObserver((entries) => {​
    
    	for (const entry of entries) {​
    	
    	if (entry.isIntersecting) {​
    	
    	console.log(`${entry.target.id} 进入可视区域`);​
    	
    	} else {​
    	
    	console.log(`${entry.target.id} 离开可视区域`);​
    	
    	}​
    	
    	}​
    
    });​
    
    observer.observe(targetElement);
    // observer.unobserve(targetElement)
    // observer.disconnect()
    

##### observe(target)
该方法用于开始观察指定的 DOM 元素 target。一旦调用此方法,当 target 与根元素的交叉状态发生变化且满足设置的阈值条件时,在构造函数中传入的回调函数就会被触发
##### unobserve(target)

用于停止观察指定的 DOM 元素 target。调用此方法后,即使 target 的交叉状态发生变化,观察器的回调函数也不会再因 target 的变化而被触发

##### disconnect()
该方法用于停止观察所有已被观察的 DOM 元素。调用此方法后,观察器将不再对任何元素的交叉状态变化做出响应

####  MutationObserver
`MutationObserver` 是浏览器提供的 **异步监听 DOM 变化** 的接口。它允许开发者监听 DOM 节点的**添加 / 删除****属性修改****文本内容变更**等操作,并在变化发生时触发回调函数。与传统的 DOM 事件(如 `DOMSubtreeModified`)相比,`MutationObserver` 更高效、更精确,且不会阻塞主线程。

```javascript
const observer = new MutationObserver(callback);
  • callback:变化发生时执行的回调函数,接收两个参数:
    • mutationsList:包含所有变化记录的数组(每个记录为 MutationRecord 对象)。
    • observer:当前的 MutationObserver 实例。
  • observe(target, options)**:开始监听指定节点的变化。
    • target:要监听的 DOM 节点。
    • options:配置对象,指定监听哪些类型的变化(必选至少一个)
{
  childList: true,         // 监听子节点的添加/删除
  attributes: true,        // 监听属性变化
  characterData: true,     // 监听文本内容变化
  subtree: true,           // 监听所有后代节点(递归)
  attributeFilter: ['id', 'class'], // 只监听特定属性
  attributeOldValue: true, // 记录变化前的属性值
  characterDataOldValue: true // 记录变化前的文本内容
}
  • disconnect():停止监听所有变化。
  • takeRecords():返回未处理的变化记录数组,并清空队列。
MutationRecord

每个变化记录包含以下属性:

  • type:变化类型('attributes''childList''characterData')。
  • target:发生变化的节点。
  • addedNodes/removedNodes:添加 / 删除的节点列表(仅 childList 变化时有效)。
  • attributeName:被修改的属性名(仅 attributes 变化时有效)。
  • oldValue:变化前的值(需在 options 中启用 attributeOldValue 或 characterDataOldValue)。