MutationObserver与ResizeObserver

6,909 阅读3分钟

1. 起因

最近在项目中遇到一个问题:需要监听某个dom元素的宽度变化,从而给此dom元素的某个子节点样式赋值。其实起初没有想到这么细,只是想到页面尺寸变化时赋值,即

// 搞个防抖
window.onresize = () => {
    return (() => {
      setTimeout(() => {
        ...
      }, 100)
    })()
  }

但是想的太简单了,后来实际操作的时候发现还需要监听dom元素的宽度,这下子有点懵,上网查了一下,发现有个MutationObserver方法,特此来学习下

3. MutationObserver

来自MDN的详解

举个例子:

<div style="width: 200px" id="parent">
    <div id="some-id"></div>
</div>
#some-id{
	width: 100%;
	height: 100px;
	background-color: aqua;
}
var targetNode = document.getElementById('some-id')
var parentNode = document.getElementById('parent')
targetNode.innerHTML = `
    <div class="text">批量设置</div>
    <input class="input" style="left: 100px;width: 100px" />
    <input class="input" style="left: 100px;width: 100px" />
  `
 
// 先创建一个回调函数,mutation.type表示监听变化的类型
var callback = function(mutationsList) {
    for(var mutation of mutationsList) {
        if (mutation.type == 'attributes') {
     		console.log('The ' + mutation.attributeName + ' attribute was modified.');
     	} else if (mutation.type == 'childList') {
            console.log('A child node has been added or removed.');
        }
    }
 }
 
 // 创建对象实例
 var observer = new MutationObserver(callback)
 
 // config 包含需要监听的类型
 var config = { attributes: true, childList: true, subtree: true }
 
 // 开始执行监听,传入监听的dom对象与config
 observer.observe(targetNode, config)
 // 先注释掉
 // 阻止 MutationObserver 实例继续接收的通知
 // observer.disconnect()
 
 setTimeout(() => {
	parentNode.style.width = '400px'
	const inputNodes = targetNode.getElementsByTagName('input')
	console.log(inputNodes[0].offsetLeft)
}, 2000)

关于config的配置: 参考

  1. childList:设置true,表示观察目标子节点的变化,比如添加或者删除目标子节点,不包括修改子节点以及子节点后代的变化
  2. attributes:设置true,表示观察目标属性的改变
  3. characterData:设置true,表示观察目标数据的改变
  4. subtree:设置为true,目标以及目标的后代改变都会观察
  5. attributeOldValue:如果属性为true或者省略,则相当于设置为true,表示需要记录改变前的目标属性值,设置了attributeOldValue可以省略attributes设置
  6. characterDataOldValue:如果characterData为true或省略,则相当于设置为true,表示需要记录改变之前的目标数据,设置了characterDataOldValue可以省略characterData设置
  7. attributeFilter:如果不是所有的属性改变都需要被观察,并且attributes设置为true或者被忽略,那么设置一个需要观察的属性本地名称(不需要命名空间)的列表

可是,跑一下上面的代码,发现并没有执行console.log('The ' + mutation.attributeName + ' attribute was modified.');明明some-id宽度都发生变化了。然后我把css的width: 100%改为100px,再让这个宽度变成200px,发现attributes条件下执行console了,原来config的attributes观察目标属性变化,是指css属性变化,width:100%,虽然宽度px是变了,但是这个属性没变,所以自然就不执行了

3. ResizeObserver

我觉得MutationObserver这个api在跟我玩文字游戏。百度不中用,科学上网后找到了个ResizeObserver

一看这mdn的介绍:

ResizeObserver 接口可以监听到 Element 的内容区域或 SVGElement的边界框改变

ResizeObserver避免了在自身回调中调整大小,从而触发的无限回调和循环依赖。
它仅通过在后续帧中处理DOM中更深层次的元素来实现这一点。
如果(浏览器)遵循规范,只会在绘制前或布局后触发调用

妥妥就是我要的api了,还是上面的例子

省略...
// 新建obsever对象
const resizeObserver = new ResizeObserver(entries => {
	for (let entry of entries) {
		console.log(entry.target.offsetWidth)
	}
});
开始监听,传入dom对象
resizeObserver.observe(targetNode);

console执行了!打印出父元素宽度变化后targetNode的宽度!!