jdcProject_front/src/hooks/useWatermark.ts

207 lines
6.5 KiB
TypeScript
Raw Normal View History

2023-08-31 17:28:05 +08:00
import { type Ref, onBeforeUnmount, ref } from "vue"
import { debounce } from "lodash-es"
2023-08-30 15:32:34 +08:00
type Observer = { mutationObserver?: MutationObserver; resizeObserver?: ResizeObserver }
2023-08-30 18:26:49 +08:00
type DefaultConfig = typeof defaultConfig
/** 默认配置 */
2023-08-30 15:32:34 +08:00
const defaultConfig = {
/** 文本颜色 */
2023-08-30 18:26:49 +08:00
color: "#c0c4cc",
2023-08-30 15:32:34 +08:00
/** 文本透明度 */
2023-08-30 18:26:49 +08:00
opacity: 0.5,
2023-08-30 15:32:34 +08:00
/** 文本字体大小 */
2023-08-31 17:28:05 +08:00
size: 16,
2023-08-30 15:32:34 +08:00
/** 文本字体 */
2023-08-30 18:26:49 +08:00
family: "serif",
/** 文本倾斜角度 */
2023-08-30 15:32:34 +08:00
angle: -20,
2023-08-31 17:28:05 +08:00
/** 一处水印所占宽度(数值越大水印密度越低) */
width: 300,
/** 一处水印所占高度(数值越大水印密度越低) */
height: 200
2023-08-30 15:32:34 +08:00
}
2023-08-30 18:26:49 +08:00
/** body 元素 */
const bodyEl = ref<HTMLElement>(document.body)
2023-08-30 15:32:34 +08:00
2023-08-31 17:28:05 +08:00
/**
*
2023-09-01 00:43:44 +08:00
* 1. body
2023-08-31 17:28:05 +08:00
* 2.
*/
2023-08-30 18:26:49 +08:00
export function useWatermark(parentEl: Ref<HTMLElement | null> = bodyEl) {
2023-08-31 17:28:05 +08:00
/** 备份文本 */
let backupText: string
2023-08-30 18:26:49 +08:00
/** 最终配置 */
let mergeConfig: DefaultConfig
/** 水印元素 */
2023-08-31 17:28:05 +08:00
let watermarkEl: HTMLElement | null = null
/** 观察器 */
const observer: Observer = {
mutationObserver: undefined,
resizeObserver: undefined
}
2023-08-30 15:32:34 +08:00
2023-08-31 17:28:05 +08:00
/** 设置水印 */
const setWatermark = (text: string, config: Partial<DefaultConfig> = {}) => {
if (!parentEl.value) {
console.warn("请在 DOM 挂载完成后再调用 setWatermark 方法设置水印")
return
2023-08-30 15:32:34 +08:00
}
2023-08-31 17:28:05 +08:00
// 备份文本
backupText = text
// 合并配置
mergeConfig = { ...defaultConfig, ...config }
// 创建水印元素
createWatermarkEl()
// 监听水印容器变化
addParentElListener(parentEl.value)
2023-08-30 15:32:34 +08:00
}
2023-08-31 17:28:05 +08:00
/** 创建水印元素 */
const createWatermarkEl = () => {
if (watermarkEl) {
updateWatermarkEl()
return
}
const div = document.createElement("div")
div.style.pointerEvents = "none"
div.style.top = "0"
div.style.left = "0"
div.style.position = "absolute"
div.style.zIndex = "99999"
watermarkEl = div
const { clientWidth, clientHeight } = parentEl.value!
updateWatermarkEl({ width: clientWidth, height: clientHeight })
// 设置水印容器为相对定位
parentEl.value!.style.position = "relative"
// 将水印元素添加到水印容器中
parentEl.value!.appendChild(div)
2023-08-30 18:26:49 +08:00
}
2023-08-31 17:28:05 +08:00
/** 更新水印元素 */
const updateWatermarkEl = (
2023-08-31 08:46:58 +08:00
options: Partial<{
width: number
height: number
}> = {}
) => {
2023-08-31 17:28:05 +08:00
if (!watermarkEl) return
backupText && (watermarkEl.style.background = `url(${createBase64()}) left top repeat`)
options.width && (watermarkEl.style.width = `${options.width}px`)
options.height && (watermarkEl.style.height = `${options.height}px`)
2023-08-30 15:32:34 +08:00
}
2023-08-31 17:28:05 +08:00
/** 创建 base64 图片 */
const createBase64 = () => {
const { color, opacity, size, family, angle, width, height } = mergeConfig
const canvasEl = document.createElement("canvas")
canvasEl.width = width
canvasEl.height = height
const ctx = canvasEl.getContext("2d")
if (ctx) {
ctx.fillStyle = color
ctx.globalAlpha = opacity
ctx.font = `${size}px ${family}`
ctx.rotate((Math.PI / 180) * angle)
ctx.fillText(backupText, 0, height / 2)
2023-08-30 15:32:34 +08:00
}
2023-08-31 17:28:05 +08:00
return canvasEl.toDataURL()
2023-08-30 15:32:34 +08:00
}
2023-08-31 17:28:05 +08:00
/** 清除水印 */
const clearWatermark = () => {
if (!parentEl.value || !watermarkEl) return
// 移除水印元素
parentEl.value.removeChild(watermarkEl)
watermarkEl = null
// 移除对水印容器的监听
removeParentElListener(parentEl.value)
2023-08-30 15:32:34 +08:00
}
2023-09-01 00:43:44 +08:00
/** 刷新水印(防御时调用) */
const updateWatermark = debounce(() => {
clearWatermark()
createWatermarkEl()
addParentElListener(parentEl.value!)
}, 100)
2023-08-31 17:28:05 +08:00
/** 监听水印容器的变化DOM 变化 & DOM 大小变化) */
const addParentElListener = (targetNode: HTMLElement) => {
2023-08-31 17:28:05 +08:00
// 防止重复添加监听
if (observer.mutationObserver || observer.resizeObserver) return
2023-08-31 17:28:05 +08:00
// 监听 DOM 变化
2023-08-31 08:46:58 +08:00
addMutationListener(targetNode)
2023-08-31 17:28:05 +08:00
// 监听 DOM 大小变化
2023-08-31 08:46:58 +08:00
addResizeListener(targetNode)
2023-08-30 15:32:34 +08:00
}
2023-08-31 17:28:05 +08:00
/** 移除对水印容器的监听 */
const removeParentElListener = (targetNode: HTMLElement) => {
2023-08-31 17:28:05 +08:00
// 移除 mutation 监听
observer.mutationObserver?.disconnect()
observer.mutationObserver = undefined
2023-08-31 17:28:05 +08:00
// 移除 resize 监听
observer.resizeObserver?.unobserve(targetNode)
observer.resizeObserver = undefined
2023-08-31 08:46:58 +08:00
}
2023-08-31 17:28:05 +08:00
/** 监听 DOM 变化 */
const addMutationListener = (targetNode: HTMLElement) => {
2023-08-31 17:28:05 +08:00
// 观察器的配置(需要观察哪些变动)
const mutationObserverOptions: MutationObserverInit = {
// 观察目标节点属性变动
2023-08-31 08:46:58 +08:00
attributes: true,
2023-08-31 17:28:05 +08:00
// 观察目标子节点是否有添加或者删除
2023-08-31 08:46:58 +08:00
childList: true,
2023-08-31 17:28:05 +08:00
// 拓展到观察所有后代节点,默认为 false
2023-08-31 08:46:58 +08:00
subtree: true
2023-08-30 15:32:34 +08:00
}
// 当观察到变动时执行的回调
2023-08-31 18:28:24 +08:00
const mutationCallback = debounce((mutationList: MutationRecord[]) => {
2023-08-31 08:46:58 +08:00
// 水印的防御(防止用户手动删除水印元素或通过 CSS 隐藏水印)
2023-09-01 00:43:44 +08:00
const defense = debounce((mutation: MutationRecord) => {
2023-08-31 08:46:58 +08:00
switch (mutation.type) {
2023-09-01 00:43:44 +08:00
case "attributes":
mutation.target === watermarkEl && updateWatermark()
break
2023-08-31 08:46:58 +08:00
case "childList":
mutation.removedNodes.forEach((item) => {
2023-08-31 17:28:05 +08:00
item === watermarkEl && targetNode.appendChild(watermarkEl)
2023-08-31 08:46:58 +08:00
})
break
}
2023-09-01 00:43:44 +08:00
}, 100)
mutationList.forEach((mutation) => {
defense(mutation)
2023-08-31 08:46:58 +08:00
})
2023-09-01 00:43:44 +08:00
}, 100)
2023-08-30 15:32:34 +08:00
// 创建一个观察器实例并传入回调
observer.mutationObserver = new MutationObserver(mutationCallback)
2023-08-30 15:32:34 +08:00
// 以上述配置开始观察目标节点
observer.mutationObserver.observe(targetNode, mutationObserverOptions)
2023-08-30 15:32:34 +08:00
}
2023-08-31 17:28:05 +08:00
/** 监听 DOM 大小变化 */
const addResizeListener = (targetNode: HTMLElement) => {
2023-08-31 08:46:58 +08:00
// 当 targetNode 元素大小变化时去更新整个水印的大小
2023-08-31 17:28:05 +08:00
const resizeCallback = debounce(() => {
2023-08-31 08:46:58 +08:00
const { clientWidth, clientHeight } = targetNode
2023-08-31 17:28:05 +08:00
updateWatermarkEl({ width: clientWidth, height: clientHeight })
2023-08-31 08:46:58 +08:00
}, 500)
2023-08-31 17:28:05 +08:00
// 创建一个观察器实例并传入回调
observer.resizeObserver = new ResizeObserver(resizeCallback)
2023-08-31 17:28:05 +08:00
// 开始观察目标节点
observer.resizeObserver.observe(targetNode)
2023-08-30 15:32:34 +08:00
}
2023-08-31 17:28:05 +08:00
/** 在组件卸载前移除水印以及各种监听 */
onBeforeUnmount(() => {
clearWatermark()
})
return { setWatermark, clearWatermark }
2023-08-30 15:32:34 +08:00
}