Template
1
0
mirror of https://github.com/un-pany/v3-admin-vite.git synced 2025-04-21 03:19:19 +08:00

perf: 新增水印防御功能开关 & 对水印监听逻辑进行极致的性能优化

This commit is contained in:
pany 2023-09-01 11:52:57 +08:00
parent 9ebed9753d
commit 2e28d0db26
2 changed files with 70 additions and 53 deletions

View File

@ -1,12 +1,18 @@
import { type Ref, onBeforeUnmount, ref } from "vue"
import { debounce } from "lodash-es"
type Observer = { mutationObserver?: MutationObserver; resizeObserver?: ResizeObserver }
type Observer = {
watermarkElMutationObserver?: MutationObserver
parentElMutationObserver?: MutationObserver
parentElResizeObserver?: ResizeObserver
}
type DefaultConfig = typeof defaultConfig
/** 默认配置 */
const defaultConfig = {
/** 防御(默认开启,能防御水印被删除或隐藏,但可能会有性能损耗) */
defense: true,
/** 文本颜色 */
color: "#c0c4cc",
/** 文本透明度 */
@ -40,8 +46,9 @@ export function useWatermark(parentEl: Ref<HTMLElement | null> = bodyEl) {
let watermarkEl: HTMLElement | null = null
/** 观察器 */
const observer: Observer = {
mutationObserver: undefined,
resizeObserver: undefined
watermarkElMutationObserver: undefined,
parentElMutationObserver: undefined,
parentElResizeObserver: undefined
}
/** 设置水印 */
@ -56,8 +63,8 @@ export function useWatermark(parentEl: Ref<HTMLElement | null> = bodyEl) {
mergeConfig = { ...defaultConfig, ...config }
// 创建水印元素
createWatermarkEl()
// 监听水印容器变化
addParentElListener(parentEl.value)
// 是否监听水印元素和容器元素的变化
mergeConfig.defense ? addElListener(parentEl.value) : removeListener()
}
/** 创建水印元素 */
@ -114,74 +121,81 @@ export function useWatermark(parentEl: Ref<HTMLElement | null> = bodyEl) {
/** 清除水印 */
const clearWatermark = () => {
if (!parentEl.value || !watermarkEl) return
// 移除对水印元素和容器元素的监听
removeListener()
// 移除水印元素
parentEl.value.removeChild(watermarkEl)
watermarkEl = null
// 移除对水印容器的监听
removeParentElListener(parentEl.value)
}
/** 刷新水印(防御时调用) */
const updateWatermark = debounce(() => {
clearWatermark()
createWatermarkEl()
addParentElListener(parentEl.value!)
addElListener(parentEl.value!)
}, 100)
/** 监听水印容器的变化DOM 变化 & DOM 大小变化) */
const addParentElListener = (targetNode: HTMLElement) => {
/** 监听水印元素和容器元素的变化DOM 变化 & DOM 大小变化) */
const addElListener = (targetNode: HTMLElement) => {
// 防止重复添加监听
if (observer.mutationObserver || observer.resizeObserver) return
if (observer.watermarkElMutationObserver || observer.parentElMutationObserver || observer.parentElResizeObserver) {
return
}
// 监听 DOM 变化
addMutationListener(targetNode)
// 监听 DOM 大小变化
addResizeListener(targetNode)
}
/** 移除对水印容器的监听 */
const removeParentElListener = (targetNode: HTMLElement) => {
/** 移除对水印元素和容器元素的监听 */
const removeListener = () => {
// 移除 mutation 监听
observer.mutationObserver?.disconnect()
observer.mutationObserver = undefined
observer.watermarkElMutationObserver?.disconnect()
observer.watermarkElMutationObserver = undefined
observer.parentElMutationObserver?.disconnect()
observer.parentElMutationObserver = undefined
// 移除 resize 监听
observer.resizeObserver?.unobserve(targetNode)
observer.resizeObserver = undefined
observer.parentElResizeObserver?.disconnect()
observer.parentElResizeObserver = undefined
}
/** 监听 DOM 变化 */
const addMutationListener = (targetNode: HTMLElement) => {
// 观察器的配置(需要观察哪些变动)
const mutationObserverOptions: MutationObserverInit = {
// 观察目标节点属性变动
attributes: true,
// 观察目标子节点是否有添加或者删除
childList: true,
// 拓展到观察所有后代节点,默认为 false
subtree: true
}
// 当观察到变动时执行的回调
const mutationCallback = debounce((mutationList: MutationRecord[]) => {
// 水印的防御(防止用户手动删除水印元素或通过 CSS 隐藏水印)
const defense = debounce((mutation: MutationRecord) => {
switch (mutation.type) {
case "attributes":
mutation.target === watermarkEl && updateWatermark()
break
case "childList":
mutation.removedNodes.forEach((item) => {
item === watermarkEl && targetNode.appendChild(watermarkEl)
})
break
}
}, 100)
mutationList.forEach((mutation) => {
defense(mutation)
})
mutationList.forEach(
debounce((mutation: MutationRecord) => {
switch (mutation.type) {
case "attributes":
mutation.target === watermarkEl && updateWatermark()
break
case "childList":
mutation.removedNodes.forEach((item) => {
item === watermarkEl && targetNode.appendChild(watermarkEl)
})
break
}
}, 100)
)
}, 100)
// 创建一个观察器实例并传入回调
observer.mutationObserver = new MutationObserver(mutationCallback)
// 创建观察器实例并传入回调
observer.watermarkElMutationObserver = new MutationObserver(mutationCallback)
observer.parentElMutationObserver = new MutationObserver(mutationCallback)
// 以上述配置开始观察目标节点
observer.mutationObserver.observe(targetNode, mutationObserverOptions)
observer.watermarkElMutationObserver.observe(watermarkEl!, {
// 观察目标节点属性是否变动,默认为 true
attributes: true,
// 观察目标子节点是否有添加或者删除,默认为 false
childList: false,
// 是否拓展到观察所有后代节点,默认为 false
subtree: false
})
observer.parentElMutationObserver.observe(targetNode, {
attributes: false,
childList: true,
subtree: false
})
}
/** 监听 DOM 大小变化 */
@ -192,9 +206,9 @@ export function useWatermark(parentEl: Ref<HTMLElement | null> = bodyEl) {
updateWatermarkEl({ width: clientWidth, height: clientHeight })
}, 500)
// 创建一个观察器实例并传入回调
observer.resizeObserver = new ResizeObserver(resizeCallback)
observer.parentElResizeObserver = new ResizeObserver(resizeCallback)
// 开始观察目标节点
observer.resizeObserver.observe(targetNode)
observer.parentElResizeObserver.observe(targetNode)
}
/** 在组件卸载前移除水印以及各种监听 */

View File

@ -15,16 +15,19 @@ const { setWatermark: setGlobalWatermark, clearWatermark: clearGlobalWatermark }
</h4>
<div ref="localRef" class="local" />
<el-button-group>
<el-button type="primary" @click="setWatermark('创建局部水印', { color: '#409eff' })">创建局部水印</el-button>
<el-button type="warning" @click="setWatermark('重置局部水印', { color: '#e6a23c' })">重置局部水印</el-button>
<el-button type="primary" @click="setWatermark('局部水印', { color: '#409eff' })">创建局部水印</el-button>
<el-button type="warning" @click="setWatermark('没有防御功能的局部水印', { color: '#e6a23c', defense: false })">
关闭防御功能
</el-button>
<el-button type="danger" @click="clearWatermark">清除局部水印</el-button>
</el-button-group>
<el-button-group>
<el-button type="primary" @click="setGlobalWatermark('创建全局水印', { color: '#409eff' })">
创建全局水印
</el-button>
<el-button type="warning" @click="setGlobalWatermark('重置全局水印', { color: '#e6a23c' })">
重置全局水印
<el-button type="primary" @click="setGlobalWatermark('全局水印', { color: '#409eff' })">创建全局水印</el-button>
<el-button
type="warning"
@click="setGlobalWatermark('没有防御功能的全局水印', { color: '#e6a23c', defense: false })"
>
关闭防御功能
</el-button>
<el-button type="danger" @click="clearGlobalWatermark">清除全局水印</el-button>
</el-button-group>