From 0a1a5ffedc58190529617c9267c6510dc7e17ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jackhoo=28=E8=83=A1=E5=BD=AA=29?= <1679575151@qq.com> Date: Mon, 11 Dec 2023 14:14:00 +0800 Subject: [PATCH] =?UTF-8?q?feat(hooks):=20useWatermark=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=B0=B4=E5=8D=B0=E9=98=B2=E7=AF=A1=E6=94=B9=E5=8A=9F=E8=83=BD?= =?UTF-8?q?(#3395)=20(#3397)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: jackhoo_98 --- src/hooks/web/useWatermark.ts | 103 +++++++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 26 deletions(-) diff --git a/src/hooks/web/useWatermark.ts b/src/hooks/web/useWatermark.ts index 0adb0ce0d..eb38f605b 100644 --- a/src/hooks/web/useWatermark.ts +++ b/src/hooks/web/useWatermark.ts @@ -4,15 +4,74 @@ import { addResizeListener, removeResizeListener } from '@/utils/event'; import { isDef } from '@/utils/is'; const watermarkSymbol = 'watermark-dom'; +const updateWatermarkText = ref(null); type UseWatermarkRes = { setWatermark: (str: string) => void; clear: () => void; clearAll: () => void; + obInstance?: MutationObserver; + targetElement?: HTMLElement; + parentElement?: HTMLElement; }; const sourceMap = new Map>(); +function createBase64(str: string) { + const can = document.createElement('canvas'); + const width = 300; + const height = 240; + Object.assign(can, { width, height }); + + const cans = can.getContext('2d'); + if (cans) { + cans.rotate((-20 * Math.PI) / 180); + cans.font = '15px Vedana'; + cans.fillStyle = 'rgba(0, 0, 0, 0.15)'; + cans.textAlign = 'left'; + cans.textBaseline = 'middle'; + cans.fillText(str, width / 20, height); + // todo 自定义水印样式 + } + return can.toDataURL('image/png'); +} +const resetWatermarkStyle = (element: HTMLElement, watermarkText: string) => { + element.className = '__' + watermarkSymbol; + element.style.pointerEvents = 'none'; + element.style.top = '0px'; + element.style.left = '0px'; + element.style.position = 'absolute'; + element.style.zIndex = '100000'; + element.style.height = '100%'; + element.style.width = '100%'; + element.style.background = `url(${createBase64( + unref(updateWatermarkText) || watermarkText, + )}) left top repeat`; +}; + +const obFn = () => { + const obInstance = new MutationObserver((mutationRecords) => { + for (const mutation of mutationRecords) { + for (const node of Array.from(mutation.removedNodes)) { + const target = Array.from(sourceMap.values()).find((item) => item.targetElement === node); + if (!target) return; + const { targetElement, parentElement } = target; + // 父元素的子元素水印如果被删除 重新插入被删除的水印(防篡改,插入通过控制台删除的水印) + if (!parentElement?.contains(targetElement as Node | null)) { + target?.parentElement?.appendChild(node as HTMLElement); + } + } + if (mutation.attributeName === 'style' && mutation.target) { + const _target = mutation.target as HTMLElement; + if (_target.className === '__' + watermarkSymbol) { + resetWatermarkStyle(_target as HTMLElement, _target?.['data-watermark-text']); + } + } + } + }); + return obInstance; +}; + export function useWatermark( appendEl: Ref = ref(document.body) as Ref, ): UseWatermarkRes { @@ -29,35 +88,17 @@ export function useWatermark( updateWatermark({ height, width }); }); const watermarkEl = shallowRef(); - const clear = () => { const domId = unref(watermarkEl); watermarkEl.value = undefined; const el = unref(appendEl); + sourceMap.has(domSymbol) && sourceMap.get(domSymbol)?.obInstance?.disconnect(); sourceMap.delete(domSymbol); if (!el) return; domId && el.removeChild(domId); removeResizeListener(el, func); }; - function createBase64(str: string) { - const can = document.createElement('canvas'); - const width = 300; - const height = 240; - Object.assign(can, { width, height }); - - const cans = can.getContext('2d'); - if (cans) { - cans.rotate((-20 * Math.PI) / 120); - cans.font = '15px Vedana'; - cans.fillStyle = 'rgba(0, 0, 0, 0.15)'; - cans.textAlign = 'left'; - cans.textBaseline = 'middle'; - cans.fillText(str, width / 20, height); - } - return can.toDataURL('image/png'); - } - function updateWatermark( options: { width?: number; @@ -80,22 +121,32 @@ export function useWatermark( const createWatermark = (str: string) => { if (unref(watermarkEl) && sourceMap.has(domSymbol)) { + updateWatermarkText.value = str; updateWatermark({ str }); return; } const div = document.createElement('div'); + div['data-watermark-text'] = str; //自定义属性 用于恢复水印 + updateWatermarkText.value = str; watermarkEl.value = div; - div.style.pointerEvents = 'none'; - div.style.top = '0px'; - div.style.left = '0px'; - div.style.position = 'absolute'; - div.style.zIndex = '100000'; + resetWatermarkStyle(div, str); const el = unref(appendEl); if (!el) return; const { clientHeight: height, clientWidth: width } = el; updateWatermark({ str, width, height }); el.appendChild(div); - sourceMap.set(domSymbol, { setWatermark, clear }); + sourceMap.set(domSymbol, { + setWatermark, + clear, + parentElement: el, + targetElement: div, + obInstance: obFn(), + }); + sourceMap.get(domSymbol)?.obInstance?.observe(el, { + childList: true, // 子节点的变动(指新增,删除或者更改) + subtree: true, // 该观察器应用于该节点的所有后代节点 + attributes: true, // 属性的变动 + }); }; function setWatermark(str: string) { @@ -108,12 +159,12 @@ export function useWatermark( }); } } - return { setWatermark, clear, clearAll }; } function clearAll() { Array.from(sourceMap.values()).forEach((item) => { + item?.obInstance?.disconnect(); item.clear(); }); }