MutationObserver原理与实践

301 阅读1分钟

概述

MutationObserver 监听dom 的任何变化,比如子元素、属性和文本内容变化;异步触发,dom改变并不会马上触发,要等到当前DOM操作都结束才触发

实例化MutationObserver

  const mutationObserverInit = {
        childList: true, // 监听目标节点添加或删除新的子节点
        subtree: true, // 将监视范围扩展至目标节点整个节点树中的所有节点
        characterData: true, // 监视指定目标节点或子节点树中节点所包含字符数据的变化
        attributeOldValue: true,
        attributes: true, // 观察受监视元素的属性值变更
      }
  el.observer.observe(document.body,mutationObserverInit)

在vue中实战

// MutationObserver 监听dom 的任何变化,比如子元素、属性和文本内容变化
// 异步触发,dom改变并不会马上触发,要等到当前DOM操作都结束才触发
import type { Directive, App } from 'vue'

interface Value {
  font: string
  textColor: string
  text: string
}

const waterMarkId = 'waterMark'
const canvasId = 'can'

const drawWaterMark = (el: HTMLElement, value: Value) => {
  const {
    font = '16px',
    textColor = 'rgba(180, 180, 180, 0.3)',
    text = '冷血出品',
  } = value
  const canvas = document.getElementById(canvasId) as HTMLCanvasElement
  const can = canvas || document.createElement('canvas')
  can.id = canvasId
  el.appendChild(can)
  can.width = 400
  can.height = 200
  can.style.display = 'none'
  const ctx = can.getContext('2d') as CanvasRenderingContext2D
  ctx?.rotate((-20 * Math.PI) / 180)
  ctx.font = font
  ctx.textAlign = 'left'
  ctx.fillStyle = textColor
  ctx.textBaseline = 'middle'
  ctx.fillText(text, can.width / 3, can.height / 2)

  const waterMaskDiv = document.createElement('div')
  waterMaskDiv.id = waterMarkId
  const styleStr = `
  width: 100%;
  height: 100%;
  position: fixed;
  z-index: -1;
  top: 0;
  left: 0;
  pointer-events: none;
  background-image: url(${can.toDataURL('image/png')})
`
  waterMaskDiv.setAttribute('style', styleStr)
  el.appendChild(waterMaskDiv)
  return styleStr
}

const vWaterMark: Directive = {
  mounted(el, { value }) {
    if (!value) return
    el.waterMarkStylestr = drawWaterMark(el, value)
    el.observer = new MutationObserver(() => {
      console.log('触发mutationObserver')
      const instance = document.getElementById(waterMarkId)
      const style = instance?.getAttribute('style')
      const { waterMarkStylestr } = el
      if ((instance && style !== waterMarkStylestr) || !instance) {
        if (instance) {
          instance.setAttribute('style', waterMarkStylestr)
        } else {
          drawWaterMark(el, value)
        }
      }
    })
    el.observer.observe(document.body, {
      childList: true,
      attributes: true,
      subtree: true,
    })
  },
  unmounted(el) {
    // 元素删除时,停止监控
    el.observer.disconnect()
    el.observer = null
  },
}
export default vWaterMark