Skip to content

DOM监听 useMutationObserver

使用 MutationObserver 观察 DOM 元素的变化的组合式函数

Show Source Code
ts
/**
 * 组合式函数
 * 使用 MutationObserver 观察 DOM 元素的变化
 *
 * 该函数提供了一个便捷的方式来订阅 DOM 元素的变动,当元素发生指定的变化时,调用提供的回调函数
 * 使用者可以指定要观察的一个或多个 DOM 元素,以及观察的选项和回调函数
 *
 * @param target 要观察的目标,可以是 Ref 对象、Ref 数组、HTMLElement 或 HTMLElement 数组
 * @param callback 当观察到变化时调用的回调函数
 * @param options MutationObserver 的观察选项,默认为空对象;例如:
 *          subtree: 是否监听以 target 为根节点的整个子树,包括子树中所有节点的属性
 *          childList: 是否监听 target 节点中发生的节点的新增与删除
 *          attributes: 是否观察所有监听的节点属性值的变化
 *          attributeFilter: 声明哪些属性名会被监听的数组;如果不声明该属性,所有属性的变化都将触发通知
 * @returns 返回一个对象,包含停止和开始观察的方法,使用者可以调用 start 方法开始观察,调用 stop 方法停止观察
 */
import { ref, toValue, computed, watch, onBeforeUnmount, onMounted, getCurrentInstance } from 'vue'
export function useMutationObserver(
  target: Ref | Ref[] | HTMLElement | HTMLElement[],
  callback: MutationCallback,
  options = {}
) {
  const isSupported = useSupported(() => window && 'MutationObserver' in window)
  const stopObservation = ref(false)
  let observer: MutationObserver | undefined
  const targets = computed(() => {
    const targetsValue = toValue(target)
    if (targetsValue) {
      if (Array.isArray(targetsValue)) {
        return targetsValue.map((el: any) => toValue(el)).filter((el: any) => el)
      } else {
        return [targetsValue]
      }
    }
    return []
  })
  // 定义清理函数,用于断开 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 stop = () => {
    stopObservation.value = true
    cleanup()
  }
  const start = () => {
    stopObservation.value = false
    observeElements()
  }
  // 在组件卸载前清理 MutationObserver
  onBeforeUnmount(() => cleanup())
  return {
    stop,
    start
  }
}
// 辅助函数
export function useSupported(callback: () => unknown) {
  const isMounted = useMounted()
  return computed(() => {
    // to trigger the ref
    isMounted.value
    return Boolean(callback())
  })
}
export function useMounted() {
  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 defaultSlotsRef = ref()
// 监听 defaultSlotsRef DOM 变化
const callback = (mutationsList: MutationRecord[], observer: MutationObserver) => {
  console.log('mutationsList', mutationsList)
  console.log('observer', observer)
}
const options = { childList: true, attributes: true, subtree: true }
useMutationObserver(defaultSlotsRef, callback, options)
</script>
<template>
  <div ref="defaultSlotsRef">
    <slot></slot>
  </div>
</template>

Params

参数说明类型默认值
target要观察的 DOM 元素或元素数组,可以是 ref 引用,也可以是 DOM 元素本身Ref | Ref[] | HTMLElement | HTMLElement[]undefined
callback当观察到变化时调用的回调函数MutationCallbackundefined
options观察选项,默认为空对象,参考文档object{}

Released under the MIT License.