网页中有水印的需求,今天我们实现手写一个防篡改水印,先看下效果图:
一、创建class
函数
传递一个dom
为水印包裹器,有一些监听防篡改的observer
,然后实例化的时候创建水印,执行create()
方法
class WaterMarker {private container: HTMLElement | null;private observer_c?: MutationObserver; // 监听水印,防篡改private observer_p?: MutationObserver; // 监听水印,防删除constructor(container: HTMLElement || null) {this.container = containerthis.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 HTMLElementel.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 = 10el.style.transform = 'rotateZ(-45deg)'el.innerHTML = contentthis.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 || 0let height = this.container.offsetHeight || 0el.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
创建,这里就不多说了