提交 | 用户 | age
|
a1d956
|
1 |
import { getCurrentInstance, onBeforeUnmount, ref, Ref, shallowRef, unref } from 'vue'; |
bab28a
|
2 |
import { useRafThrottle } from '@/utils/domUtils'; |
X |
3 |
import { addResizeListener, removeResizeListener } from '@/utils/event'; |
|
4 |
import { isDef } from '@/utils/is'; |
2f6253
|
5 |
|
64b812
|
6 |
const watermarkSymbol = 'watermark-dom'; |
0a1a5f
|
7 |
const updateWatermarkText = ref<string | null>(null); |
64b812
|
8 |
|
IW |
9 |
type UseWatermarkRes = { |
|
10 |
setWatermark: (str: string) => void; |
|
11 |
clear: () => void; |
|
12 |
clearAll: () => void; |
ca3ddd
|
13 |
waterMarkOptions?: waterMarkOptionsType; |
0a1a5f
|
14 |
obInstance?: MutationObserver; |
J |
15 |
targetElement?: HTMLElement; |
|
16 |
parentElement?: HTMLElement; |
64b812
|
17 |
}; |
IW |
18 |
|
ca3ddd
|
19 |
type waterMarkOptionsType = { |
K |
20 |
// 自定义水印的文字大小 |
|
21 |
fontSize?: number; |
|
22 |
// 自定义水印的文字颜色 |
|
23 |
fontColor?: string; |
|
24 |
// 自定义水印的文字字体 |
|
25 |
fontFamily?: string; |
|
26 |
// 自定义水印的文字对齐方式 |
|
27 |
textAlign?: CanvasTextAlign; |
|
28 |
// 自定义水印的文字基线 |
|
29 |
textBaseline?: CanvasTextBaseline; |
|
30 |
// 自定义水印的文字倾斜角度 |
|
31 |
rotate?: number; |
|
32 |
}; |
|
33 |
|
64b812
|
34 |
const sourceMap = new Map<Symbol, Omit<UseWatermarkRes, 'clearAll'>>(); |
0a1a5f
|
35 |
|
ca3ddd
|
36 |
function findTargetNode(el) { |
K |
37 |
return Array.from(sourceMap.values()).find((item) => item.targetElement === el); |
|
38 |
} |
|
39 |
|
|
40 |
function createBase64(str: string, waterMarkOptions: waterMarkOptionsType) { |
0a1a5f
|
41 |
const can = document.createElement('canvas'); |
J |
42 |
const width = 300; |
|
43 |
const height = 240; |
|
44 |
Object.assign(can, { width, height }); |
|
45 |
|
|
46 |
const cans = can.getContext('2d'); |
|
47 |
if (cans) { |
ca3ddd
|
48 |
const fontFamily = waterMarkOptions?.fontFamily || 'Vedana'; |
K |
49 |
const fontSize = waterMarkOptions?.fontSize || 15; |
|
50 |
const fontColor = waterMarkOptions?.fontColor || 'rgba(0, 0, 0, 0.15)'; |
|
51 |
const textAlign = waterMarkOptions?.textAlign || 'left'; |
|
52 |
const textBaseline = waterMarkOptions?.textBaseline || 'middle'; |
|
53 |
const rotate = waterMarkOptions?.rotate || 20; |
|
54 |
cans.rotate((-rotate * Math.PI) / 180); |
|
55 |
cans.font = `${fontSize}px ${fontFamily}`; |
|
56 |
cans.fillStyle = fontColor; |
|
57 |
cans.textAlign = textAlign; |
|
58 |
cans.textBaseline = textBaseline; |
0a1a5f
|
59 |
cans.fillText(str, width / 20, height); |
J |
60 |
} |
|
61 |
return can.toDataURL('image/png'); |
|
62 |
} |
ca3ddd
|
63 |
const resetWatermarkStyle = ( |
K |
64 |
element: HTMLElement, |
|
65 |
watermarkText: string, |
|
66 |
waterMarkOptions: waterMarkOptionsType, |
|
67 |
) => { |
0a1a5f
|
68 |
element.className = '__' + watermarkSymbol; |
J |
69 |
element.style.pointerEvents = 'none'; |
ca3ddd
|
70 |
element.style.display = 'block'; |
K |
71 |
element.style.visibility = 'visible'; |
0a1a5f
|
72 |
element.style.top = '0px'; |
J |
73 |
element.style.left = '0px'; |
|
74 |
element.style.position = 'absolute'; |
|
75 |
element.style.zIndex = '100000'; |
|
76 |
element.style.height = '100%'; |
|
77 |
element.style.width = '100%'; |
|
78 |
element.style.background = `url(${createBase64( |
|
79 |
unref(updateWatermarkText) || watermarkText, |
ca3ddd
|
80 |
waterMarkOptions, |
0a1a5f
|
81 |
)}) left top repeat`; |
J |
82 |
}; |
|
83 |
|
|
84 |
const obFn = () => { |
|
85 |
const obInstance = new MutationObserver((mutationRecords) => { |
|
86 |
for (const mutation of mutationRecords) { |
|
87 |
for (const node of Array.from(mutation.removedNodes)) { |
ca3ddd
|
88 |
const target = findTargetNode(node); |
0a1a5f
|
89 |
if (!target) return; |
J |
90 |
const { targetElement, parentElement } = target; |
|
91 |
// 父元素的子元素水印如果被删除 重新插入被删除的水印(防篡改,插入通过控制台删除的水印) |
|
92 |
if (!parentElement?.contains(targetElement as Node | null)) { |
|
93 |
target?.parentElement?.appendChild(node as HTMLElement); |
|
94 |
} |
|
95 |
} |
9784cd
|
96 |
if (mutation.type === 'attributes' && mutation.target) { |
J |
97 |
// 修复控制台可以”Hide element” 的问题 |
0a1a5f
|
98 |
const _target = mutation.target as HTMLElement; |
ca3ddd
|
99 |
const target = findTargetNode(_target); |
K |
100 |
if (target) { |
9784cd
|
101 |
// 禁止改属性 包括class 修改以后 mutation.type 也等于 'attributes' |
J |
102 |
// 先解除监听 再加一下 |
|
103 |
clearAll(); |
|
104 |
target.setWatermark(target.targetElement?.['data-watermark-text']); |
0a1a5f
|
105 |
} |
J |
106 |
} |
|
107 |
} |
|
108 |
}); |
|
109 |
return obInstance; |
|
110 |
}; |
2f6253
|
111 |
|
480cfb
|
112 |
export function useWatermark( |
56a966
|
113 |
appendEl: Ref<HTMLElement | null> = ref(document.body) as Ref<HTMLElement>, |
ca3ddd
|
114 |
waterMarkOptions: waterMarkOptionsType = {}, |
64b812
|
115 |
): UseWatermarkRes { |
IW |
116 |
const domSymbol = Symbol(watermarkSymbol); |
3b0077
|
117 |
const appendElRaw = unref(appendEl); |
64b812
|
118 |
if (appendElRaw && sourceMap.has(domSymbol)) { |
IW |
119 |
const { setWatermark, clear } = sourceMap.get(domSymbol) as UseWatermarkRes; |
|
120 |
return { setWatermark, clear, clearAll }; |
3b0077
|
121 |
} |
a1d956
|
122 |
const func = useRafThrottle(function () { |
Y |
123 |
const el = unref(appendEl); |
|
124 |
if (!el) return; |
|
125 |
const { clientHeight: height, clientWidth: width } = el; |
|
126 |
updateWatermark({ height, width }); |
|
127 |
}); |
|
128 |
const watermarkEl = shallowRef<HTMLElement>(); |
|
129 |
const clear = () => { |
|
130 |
const domId = unref(watermarkEl); |
|
131 |
watermarkEl.value = undefined; |
|
132 |
const el = unref(appendEl); |
0a1a5f
|
133 |
sourceMap.has(domSymbol) && sourceMap.get(domSymbol)?.obInstance?.disconnect(); |
64b812
|
134 |
sourceMap.delete(domSymbol); |
a1d956
|
135 |
if (!el) return; |
Y |
136 |
domId && el.removeChild(domId); |
|
137 |
removeResizeListener(el, func); |
|
138 |
}; |
2f6253
|
139 |
|
a1d956
|
140 |
function updateWatermark( |
Y |
141 |
options: { |
|
142 |
width?: number; |
|
143 |
height?: number; |
|
144 |
str?: string; |
56a966
|
145 |
} = {}, |
a1d956
|
146 |
) { |
Y |
147 |
const el = unref(watermarkEl); |
|
148 |
if (!el) return; |
|
149 |
if (isDef(options.width)) { |
|
150 |
el.style.width = `${options.width}px`; |
|
151 |
} |
|
152 |
if (isDef(options.height)) { |
|
153 |
el.style.height = `${options.height}px`; |
|
154 |
} |
|
155 |
if (isDef(options.str)) { |
ca3ddd
|
156 |
el.style.background = `url(${createBase64(options.str, waterMarkOptions)}) left top repeat`; |
a1d956
|
157 |
} |
Y |
158 |
} |
|
159 |
|
|
160 |
const createWatermark = (str: string) => { |
64b812
|
161 |
if (unref(watermarkEl) && sourceMap.has(domSymbol)) { |
0a1a5f
|
162 |
updateWatermarkText.value = str; |
a1d956
|
163 |
updateWatermark({ str }); |
64b812
|
164 |
return; |
a1d956
|
165 |
} |
2f6253
|
166 |
const div = document.createElement('div'); |
0a1a5f
|
167 |
div['data-watermark-text'] = str; //自定义属性 用于恢复水印 |
J |
168 |
updateWatermarkText.value = str; |
a1d956
|
169 |
watermarkEl.value = div; |
ca3ddd
|
170 |
resetWatermarkStyle(div, str, waterMarkOptions); |
2f6253
|
171 |
const el = unref(appendEl); |
64b812
|
172 |
if (!el) return; |
a1d956
|
173 |
const { clientHeight: height, clientWidth: width } = el; |
Y |
174 |
updateWatermark({ str, width, height }); |
|
175 |
el.appendChild(div); |
0a1a5f
|
176 |
sourceMap.set(domSymbol, { |
J |
177 |
setWatermark, |
|
178 |
clear, |
|
179 |
parentElement: el, |
|
180 |
targetElement: div, |
|
181 |
obInstance: obFn(), |
ca3ddd
|
182 |
waterMarkOptions, |
0a1a5f
|
183 |
}); |
J |
184 |
sourceMap.get(domSymbol)?.obInstance?.observe(el, { |
|
185 |
childList: true, // 子节点的变动(指新增,删除或者更改) |
|
186 |
subtree: true, // 该观察器应用于该节点的所有后代节点 |
|
187 |
attributes: true, // 属性的变动 |
|
188 |
}); |
2f6253
|
189 |
}; |
陈 |
190 |
|
|
191 |
function setWatermark(str: string) { |
|
192 |
createWatermark(str); |
a1d956
|
193 |
addResizeListener(document.documentElement, func); |
2f6253
|
194 |
const instance = getCurrentInstance(); |
陈 |
195 |
if (instance) { |
|
196 |
onBeforeUnmount(() => { |
|
197 |
clear(); |
|
198 |
}); |
|
199 |
} |
|
200 |
} |
64b812
|
201 |
return { setWatermark, clear, clearAll }; |
IW |
202 |
} |
|
203 |
|
|
204 |
function clearAll() { |
|
205 |
Array.from(sourceMap.values()).forEach((item) => { |
0a1a5f
|
206 |
item?.obInstance?.disconnect(); |
64b812
|
207 |
item.clear(); |
IW |
208 |
}); |
2f6253
|
209 |
} |