DOM监听 useMutationObserver
赞助
使用 MutationObserver 观察 DOM 元素的变化的组合式函数
Show Source Code
ts
/**
* 组合式函数
* 使用 MutationObserver 观察 DOM 元素的变化
*
* 该函数提供了一个便捷的方式来订阅 DOM 元素的变动,当元素发生指定的变化时,调用提供的回调函数
* 使用者可以指定要观察的一个或多个 DOM 元素,以及观察的选项和回调函数
*
* @param {Ref | Ref[] | HTMLElement | HTMLElement[]} target 要观察的目标,可以是 Ref 对象、Ref 数组、HTMLElement 或 HTMLElement 数组
* @param {MutationCallback} callback 当观察到变化时调用的回调函数
* @param {object} [options = {}] MutationObserver 的观察选项,默认为空对象;例如:
* subtree: 是否监听以 target 为根节点的整个子树,包括子树中所有节点的属性
* childList: 是否监听 target 节点中发生的节点的新增与删除
* attributes: 是否观察所有监听的节点属性值的变化
* attributeFilter: 声明哪些属性名会被监听的数组;如果不声明该属性,所有属性的变化都将触发通知
* @returns {start: () => void, stop: () => void} 返回一个对象,包含停止和开始观察的方法,使用者可以调用 start 方法开始观察,调用 stop 方法停止观察
*/
import { ref, toValue, computed, watch, onBeforeUnmount, onMounted, getCurrentInstance } from 'vue'
import type { Ref, ComputedRef } from 'vue'
/**
* 归一化观察目标为 HTMLElement 数组
*
* 兼容 Ref / Ref[] / HTMLElement / HTMLElement[] 四种入参:先解包 Ref,再过滤空值,
* 保证后续 observe 调用拿到的都是可用元素。
*/
function resolveTargetElements(target: Ref | Ref[] | HTMLElement | HTMLElement[]): HTMLElement[] {
const targetValue = toValue(target) as Ref | Ref[] | HTMLElement | HTMLElement[] | null | undefined
if (!targetValue) return []
const list = Array.isArray(targetValue) ? targetValue : [targetValue]
return list
.map((item) => toValue(item) as HTMLElement | null | undefined)
.filter((element): element is HTMLElement => Boolean(element))
}
export function useMutationObserver(
target: Ref | Ref[] | HTMLElement | HTMLElement[],
callback: MutationCallback,
options: object = {}
): { start: () => void; stop: () => void } {
// 用 typeof 判断而非裸 window:SSR(Node)下裸引用会直接抛 ReferenceError
const isSupported = useSupported(() => typeof window !== 'undefined' && 'MutationObserver' in window)
const stopObservation = ref(false)
let observer: MutationObserver | undefined
const targets = computed(() => resolveTargetElements(target))
// 定义清理函数,用于断开 MutationObserver 的连接
const cleanup = () => {
if (observer) {
observer.disconnect()
observer = undefined
}
}
// 初始化 MutationObserver,开始观察目标元素
const observeElements = () => {
if (isSupported.value && targets.value.length && !stopObservation.value) {
observer = new MutationObserver(callback)
targets.value.forEach((element: HTMLElement) => observer!.observe(element, options))
}
}
// 监听 targets 的变化,当 targets 变化时,重新建立 MutationObserver 观察
watch(
() => targets.value,
() => {
cleanup()
observeElements()
},
{
immediate: true, // 立即触发回调,以便初始状态也被观察
flush: 'post'
}
)
const start = () => {
stopObservation.value = false
observeElements()
}
const stop = () => {
stopObservation.value = true
cleanup()
}
// 在组件卸载前清理 MutationObserver
onBeforeUnmount(() => cleanup())
return {
start,
stop
}
}
export function useSupported(callback: () => unknown): ComputedRef<boolean> {
const isMounted = useMounted()
return computed(() => {
// to trigger the ref
isMounted.value
return Boolean(callback())
})
}
export function useMounted(): Ref<boolean> {
const isMounted = ref(false)
// 获取当前组件的实例
const instance = getCurrentInstance()
if (instance) {
onMounted(() => {
isMounted.value = true
}, instance)
}
return isMounted
}基本使用
监听容器内节点的增删,并实时统计变更次数
新增节点
删除节点
停止观察
开始观察
vue
<script setup lang="ts">
import { ref } from 'vue'
import { useMutationObserver } from 'vue-amazing-ui'
const observerRef = ref<HTMLDivElement | null>(null)
const mutationCount = ref(0)
const itemId = ref(0)
const callback = (mutationsList: MutationRecord[]) => {
mutationCount.value += mutationsList.length
}
const options = { childList: true, attributes: true, subtree: true }
const { start, stop } = useMutationObserver(observerRef, callback, options)
function addItem() {
const item = document.createElement('div')
item.className = 'observer-item'
item.textContent = `节点 ${++itemId.value}`
observerRef.value?.appendChild(item)
}
function removeItem() {
observerRef.value?.lastElementChild?.remove()
}
</script>
<template>
<Flex vertical>
<Space :gap="8">
<Button type="primary" @click="addItem">新增节点</Button>
<Button @click="removeItem">删除节点</Button>
<Button @click="stop">停止观察</Button>
<Button @click="start">开始观察</Button>
</Space>
<Alert type="info" :message="`已观察到 ${mutationCount} 条变更记录`" />
<div ref="observerRef" class="observer-container"></div>
</Flex>
</template>
<style lang="less" scoped>
.observer-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
min-height: 52px;
padding: 8px 12px;
border: 1px dashed rgba(0, 0, 0, 0.15);
border-radius: 6px;
:deep(.observer-item) {
height: fit-content;
padding: 4px 10px;
font-size: 14px;
background: #f0f5ff;
border-radius: 4px;
}
}
</style>Params
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| target | 要观察的 DOM 元素或元素数组,可以是 ref 引用,也可以是 DOM 元素本身 | Ref | Ref[] | HTMLElement | HTMLElement[] | undefined |
| callback | 当观察到变化时调用的回调函数 | MutationCallback | undefined |
| options | 观察选项,默认为空对象,参考文档 | object | {} |
Return
| 名称 | 说明 | 类型 |
|---|---|---|
| start | 开始观察目标元素 | () => void |
| stop | 停止观察并断开与目标元素的连接 | () => void |