在实现一个项目需求的时候,需要监听到某个div元素的宽高变化,第一时间想到的是resize事件,但是很不幸运的是,resize事件只能加在window对象上,并不能监听具体某个DOM元素。
多方查阅之后,了解到MutationObserver,这是一个可以用来监听整个DOM中任何变化的东西,可以把它理解为一个类,实例化之后调用类实例的几个简单接口即可完成监听,以下具体介绍。
一、MutationObserver介绍
1.构造函数为window.MutationObserver,参数为一个回调函数。
监控到DOM中的改变并且等待一系列改变结束后就会触发回调函数。它与事件的不同之处在于,它在DOM变化时,会记录每一个DOM的变化(为一个MutationRecord对象),但是到DOM变化结束时触发回调。DOM变化可能是一系列的(比如元素的宽和高同时改变),那么这一系列的变化就会产生一个队列,这个队列会作为参数传递给回调函数。
由于浏览器差异的原因,一些版本的浏览器各自支持了构造函数,但是用法都是一样的,实例化一个观察者的代码如下:
let MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver
let observer = new MutationObserver(callback)
2.调用接口开始监控DOM。
常用的接口有三个:
①observe(element, options) 配置MutationObserver
在DOM更改匹配给定选项时,通过其回调函数开始接收通知。
element即要监听的DOM元素,options为监听选项对象,可选的选项如下:
MutationObserver监听选项
选项 | 取值 | 描述 |
childList | true/false | 设置为true表示监听指定元素的子元素的变动 |
attributes | true/false | 设置为true表示监听指定元素的属性的变动 |
characterData | true/false | 设置为true表示监听指定元素的data变动 |
subtree | true/false | 设置为true表示不仅监听目标元素也同时监听其子元素变动,不能单独设置此选项,必须至少包含前三个选项中的一个 |
attributeOldValue | true/false | 在 |
characterDataOldValue | true/false | 在 |
attributeFilter | 监听的属性数组,比如['style',...] | 配置监听数组内的属性变化,数组外的属性变化会被忽略 |
所以监听元素宽高变化,就是监听其style属性变化:
observer.observe(element, { attributes: true, attributeFilter: ['style'], attributeOldValue: true })
这样当元素的style发生改变的时候,就会触发构造函数中传入的callback函数。
②disconnect() 阻止 MutationObserver
实例继续接收的通知,直到再次调用其observe方法,该观察者对象包含的回调函数都不会再被调用。
③takeRecords() 从MutationObserver的通知队列中删除所有待处理的通知,并将它们返回到一个MutationRecord对象构成的新数组中。
二、示例
这里以Vue中的一个组件作为实例,了解了以上所述内容后其实非常简单,代码如下:
<template>
<div class="container">
<div class="resize-element">
改变大小试试
</div>
<div class="resize-record">
触发了{{firedNum}}次resize事件。
</div>
</div>
</template>
<script>
export default {
showName: '监听DOM变化',
data () {
return {
observer: null,
firedNum: 0,
recordOldValue: { // 记录下旧的宽高数据,避免重复触发回调函数
width: '0',
height: '0'
}
}
},
mounted () {
let MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver
let element = document.querySelector('.resize-element')
this.observer = new MutationObserver((mutationList) => {
for (let mutation of mutationList) {
console.log(mutation)
}
let width = getComputedStyle(element).getPropertyValue('width')
let height = getComputedStyle(element).getPropertyValue('height')
if (width === this.recordOldValue.width && height === this.recordOldValue.height) return
this.recordOldValue = {
width,
height
}
this.firedNum += 1
})
this.observer.observe(element, { attributes: true, attributeFilter: ['style'], attributeOldValue: true })
},
beforeDestroyed () {
if (this.observer) {
this.observer.disconnect()
this.observer.takeRecords()
this.observer = null
}
}
}
</script>
<style lang="stylus" scoped>
.container
position relative
.resize-element
transform translate(-50%, -50%)
position absolute
top 50%
left 50%
height 10rem
width 10rem
overflow hidden
resize both
display block
box-shadow 0 0 1px 1px #3361D8
border-radius 2px
</style>
这里记录了旧的宽高数据来避免重复触发回调函数,这样做的原因在于宽高数据改变时,不一定是整数,而MutationRecord.recordOldValue中记录的是取整后的数据,这样就会导致在拖动改变DOM元素的宽高时,数值一直在整数和小数之间跳动,会多次触发。