wip: 水印 hook 优化 (#126)

This commit is contained in:
ClariS 2023-08-31 08:46:58 +08:00 committed by GitHub
parent 2c7d5b2b76
commit 2ea0a80160
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,7 +1,7 @@
import { type Ref, getCurrentInstance, onBeforeUnmount, ref, shallowRef } from "vue" import { type Ref, getCurrentInstance, onBeforeUnmount, ref, shallowRef } from "vue"
import { throttle } from "lodash-es" import { throttle } from "lodash-es"
type TargetNode<T> = T & { _observer?: MutationObserver } type TargetNode<T> = T & { _mutationObserver?: MutationObserver; _resizeObserver?: ResizeObserver }
type DefaultConfig = typeof defaultConfig type DefaultConfig = typeof defaultConfig
@ -54,17 +54,17 @@ export function useWatermark(parentEl: Ref<HTMLElement | null> = bodyEl) {
if (!parentEl.value || !watermarkEl.value) return if (!parentEl.value || !watermarkEl.value) return
parentEl.value.removeChild(watermarkEl.value) parentEl.value.removeChild(watermarkEl.value)
watermarkEl.value = null watermarkEl.value = null
// 移除对水印容器的监听 // 移除对水印容器的监听DOM 变化 & DOM 大小变化)
removeResizeListener(parentEl.value) removeDomListener(parentEl.value)
} }
function updateWatermark( const updateWatermark = (
options: { options: Partial<{
width?: number width: number
height?: number height: number
text?: string text: string
} = {} }> = {}
) { ) => {
if (!watermarkEl.value) return if (!watermarkEl.value) return
options.width && (watermarkEl.value.style.width = `${options.width}px`) options.width && (watermarkEl.value.style.width = `${options.width}px`)
options.height && (watermarkEl.value.style.height = `${options.height}px`) options.height && (watermarkEl.value.style.height = `${options.height}px`)
@ -83,7 +83,7 @@ export function useWatermark(parentEl: Ref<HTMLElement | null> = bodyEl) {
div.style.top = "0" div.style.top = "0"
div.style.left = "0" div.style.left = "0"
div.style.position = "absolute" div.style.position = "absolute"
div.style.zIndex = "100000" div.style.zIndex = "9999"
const { clientWidth, clientHeight } = parentEl.value const { clientWidth, clientHeight } = parentEl.value
updateWatermark({ width: clientWidth, height: clientHeight, text }) updateWatermark({ width: clientWidth, height: clientHeight, text })
parentEl.value.appendChild(div) parentEl.value.appendChild(div)
@ -97,55 +97,75 @@ export function useWatermark(parentEl: Ref<HTMLElement | null> = bodyEl) {
mergeConfig = { ...defaultConfig, ...config } mergeConfig = { ...defaultConfig, ...config }
// 创建水印 // 创建水印
createWatermark(text) createWatermark(text)
// 监听水印容器变化 // 监听水印容器变化DOM 变化 & DOM 大小变化)
addResizeListener(parentEl.value) addDomListener(parentEl.value)
// 当前组件实例卸载前删除水印 // 当前组件实例卸载前删除水印
getCurrentInstance() && onBeforeUnmount(clear) getCurrentInstance() && onBeforeUnmount(clear)
} }
const defendRemoveWatermark = (mutationList: MutationRecord[], targetNode: HTMLElement) => { const addDomListener = (targetNode: TargetNode<HTMLElement>) => {
mutationList.forEach((mutation) => { if (targetNode._mutationObserver || targetNode._resizeObserver || !watermarkEl.value) return
switch (mutation.type) { // 监听 mutation 变化DOM 变化)
case "childList": addMutationListener(targetNode)
mutation.removedNodes.forEach((item) => { // 监听 resize 变化DOM 大小变化)
item === watermarkEl.value && targetNode.appendChild(watermarkEl.value) addResizeListener(targetNode)
}) }
break
case "attributes": const removeDomListener = (targetNode: TargetNode<HTMLElement>) => {
if (watermarkEl.value!.style.display === "none") { // 取消 mutation 监听
watermarkEl.value!.style.display = "block" targetNode._mutationObserver?.disconnect()
} targetNode._mutationObserver = undefined
break // 取消 resize 监听
} targetNode._resizeObserver?.unobserve(targetNode)
}) targetNode._resizeObserver = undefined
}
const addMutationListener = (targetNode: TargetNode<HTMLElement>) => {
// 观察器的配置(需要观察什么变动)
const observerOptions: MutationObserverInit = {
// 观察属性变动
attributes: true,
// 观察目标子节点的变化,是否有添加或者删除
childList: true,
// 观察后代节点,默认为 false
subtree: true
}
// 当观察到变动时执行的回调
const mutationCallback = (mutationList: MutationRecord[]) => {
// 水印的防御(防止用户手动删除水印元素或通过 CSS 隐藏水印)
mutationList.forEach((mutation) => {
switch (mutation.type) {
case "childList":
mutation.removedNodes.forEach((item) => {
item === watermarkEl.value && targetNode.appendChild(watermarkEl.value)
})
break
case "attributes":
if (watermarkEl.value!.style.display === "none") {
watermarkEl.value!.style.display = "block"
} else if (watermarkEl.value!.style.visibility === "hidden") {
watermarkEl.value!.style.visibility = "visible"
}
break
}
})
}
// 创建一个观察器实例并传入回调
const mutationObserver = new MutationObserver(mutationCallback)
// 以上述配置开始观察目标节点
mutationObserver.observe(targetNode, observerOptions)
targetNode._mutationObserver = mutationObserver
} }
const addResizeListener = (targetNode: TargetNode<HTMLElement>) => { const addResizeListener = (targetNode: TargetNode<HTMLElement>) => {
if (targetNode._observer || !watermarkEl.value) return // 当 targetNode 元素大小变化时去更新整个水印的大小
// 观察器的配置(需要观察什么变动) const resizeCallback = throttle(() => {
const observerOptions: MutationObserverInit = {
// 观察目标子节点的变化,是否有添加或者删除
attributes: true,
// 观察属性变动
childList: true
}
// 当观察到变动时执行的回调
const callback = throttle((mutationList: MutationRecord[]) => {
const { clientWidth, clientHeight } = targetNode const { clientWidth, clientHeight } = targetNode
updateWatermark({ width: clientWidth, height: clientHeight }) updateWatermark({ width: clientWidth, height: clientHeight })
// 明水印的防御
defendRemoveWatermark(mutationList, targetNode)
}, 500) }, 500)
// 创建一个观察器实例并传入回调 const resizeObserver = new ResizeObserver(resizeCallback)
const observer = new MutationObserver(callback) resizeObserver.observe(targetNode)
// 以上述配置开始观察目标节点 targetNode._resizeObserver = resizeObserver
observer.observe(targetNode, observerOptions)
targetNode._observer = observer
}
const removeResizeListener = (targetNode: TargetNode<HTMLElement>) => {
targetNode._observer?.disconnect()
targetNode._observer = undefined
} }
return { setWatermark, clear } return { setWatermark, clear }