网页中有水印的需求,今天我们实现手写一个防篡改水印,先看下效果图:
一、创建class
函数
传递一个dom
为水印包裹器,有一些监听防篡改的observer
,然后实例化的时候创建水印,执行create()
方法
class WaterMarker {
private container: HTMLElement | null;
private observer_c?: MutationObserver; // 监听水印,防篡改
private observer_p?: MutationObserver; // 监听水印,防删除
constructor(container: HTMLElement || null) {
this.container = container
this.create() // 初始化
}
// 获取水印元素
getElement(): HTMLElement | null {
return document.getElementById(`water_marker`)
}
// 延时执行
async wait(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}
}
let dom = document.querySelector('#body') as HTMLElement || null
dom ? let waterMarker = new WaterMarker(dom) : ''
二、创建水印dom
这里创建水印dom
记得将鼠标指针设置为无效,以免水印dom
遮挡画面部分点击区域,‘pointerEvents’ 为 null
,这里的 mixBlendMode
混合模式则是为了避免水印和画面的颜色重叠,造成水印不可见。
// 创建水印
async create(): Promise<void> {
await this.wait(500)
let content = `<span style="font-size: 18px">游客</span> <br> <span style="font-size: 18px">Guest</span> <br> 中国传媒大学`
let el = document.createElement('div') as HTMLElement
el.style.display = 'inline-block'
el.style.position = 'absolute'
el.id = `water_marker`
el.style.top = '20px'
el.style.left = '20px'
el.style.lineHeight = '20px'
el.style.color = '#fff'
el.style.fontSize = '14px'
el.style.textAlign = 'center'
el.style.fontWeight = 'bold'
el.style.pointerEvents = 'none'
el.style.filter = 'opacity(0.75)'
el.style.textShadow = '1px 1px 0px rgba(0, 0, 0, 0.2),\n' +
' 1px 2px 0px rgba(0, 0, 0, 0.2),\n' +
' 1px 3px 0px rgba(0, 0, 0, 0.2)'
el.style.mixBlendMode = 'difference'
el.style.zIndex = 10
el.style.transform = 'rotateZ(-45deg)'
el.innerHTML = content
this.container?.appendChild(el)
// 开始动画
this.createAnimate(el)
// 等待一秒开始监听水印篡改,删除
await this.wait(1000)
// 监听水印,防篡改
this.observeC(el)
// 监听水印,防删除
this.observeP()
}
三、给水印添加animate
动画,旋转动画
// 创建动画
createAnimate(el: HTMLElement): void {
let delay = 1000 * 6 // 6秒循环一次
let width = this.container.offsetWidth || 0
let height = this.container.offsetHeight || 0
el.animate(
[
{ transform: `translate(0 ,0) rotateZ(-45deg)` }, // 起始位置
{ transform: `translate(calc(${width - 40}px - 100%), 0) rotateZ(45deg)`}, // 右侧
{ transform: `translate(calc(${width - 40}px - 100%), calc(${height - 40}px - 100%)) rotateZ(45deg)` }, // 底部
{ transform: `translate(0, calc(${height - 40}px - 100%)) rotateZ(-45deg)` }, // 左侧
{ transform: `translate(0, 0) rotateZ(-45deg)`} // 返回起始位置
],
{
duration: delay,
iterations: Infinity, // 无限循环
easing: 'cubic-bezier(.31,.01,1,1.27)',
delay: 0,
}
)
}
四、水印添加防篡改,防删除
防篡改用MutationObserver
监听目标元素的节点、属性、子节点变化达到防篡改的作用
防删除用MutationObserver
监听包裹器,判断删除的子节点是否包含水印,达到防删除的作用
这里的option
配置有几个属性:
属性名 | 作用 |
---|---|
attributes | 元素节点的属性值变化 |
characterData | 元素节点的内容数据发生变化 |
childList | 元素节点的子节点的新增和移除 |
subtree | 元素深层节点的变化 |
// 监听目标元素的变化
observeC(el: HTMLElement): void {
let config: MutationObserverInit = { attributes: true, characterData: true, childList: true, subtree: true }
this.observer_c = new MutationObserver((mutationsList: MutationRecord[]) => {
for (let mutation of mutationsList) {
switch (mutation.type) {
case 'attributes':
case 'childList':
case 'characterData':
alert('请勿篡改水印哦,尊重版权!')
// 销毁重新创建,这里可以函数防抖下,改变style的时候
let cb = () => {
this.destroy()
this.create()
}
debounce(cb, 100)
break;
default:
break;
}
}
});
this.observer_c.observe(el, config);
}
// 监听目标元素的父元素的子节点删除,从而达到监听水印被删除
observeP() {
let config: MutationObserverInit = { attributes: true, characterData: true, childList: true, subtree: true }
this.observer_p = new MutationObserver((mutationsList: MutationRecord[]) => {
for (let mutation of mutationsList) {
switch (mutation.type) {
case 'childList':
// 判断删除的子节点中是否包含水印节点
let removedNodes = Array.from(mutation.removedNodes)
if (removedNodes.find(node => node.id.includes('water_marker'))) {
alert('请勿篡改水印哦,尊重版权!')
// 销毁重新创建
this.destroy()
this.create()
}
break;
default:
break;
}
}
});
this.observer_p.observe(this.container, config);
}
五、添加销毁事件
这里要先销毁监听器,再销毁水印dom
destroy() {
this.observer_c?.disconnect()
this.observer_p?.disconnect()
let el: HTMLElement = this.getElement()
el? el.remove() : ''
}
六、拓展
如果涉及到重复,创建dom
可修改为canvas
创建,这里就不多说了