mutationobserver 是防删水印的必选项,因其能实时捕获 removechild 等 dom 变动,需监听 document.body 并配置 childlist、subtree、attributes,通过 removednodes 判断并用 clonenode(true) 备份节点恢复。

水印能被删掉,不是因为你没写对,而是你只写了“画出来”,没写“盯住它”。 MutationObserver 不是防删的银弹,但它是目前唯一能实时拦截 DOM 删除、属性篡改、子节点变动的原生机制。靠 display: none 或手动重插,根本挡不住开发者按一下 Delete 键。
为什么 MutationObserver 是防删水印的必选项
浏览器控制台里选中水印元素后按 Delete,DOM 节点立刻消失——这不是 bug,是标准行为。CSS 方案(如 background-image)连节点都没有,更谈不上“删”;而用 JS 创建的 div 水印,只要没监听,就等于裸奔。
- MutationObserver 是唯一能在
removeChild执行后、但页面还没重绘前捕获到“它被删了”的 API - 它不依赖轮询,不卡主线程,变化批量异步触发,性能可控
- 必须监听
childList: true和subtree: true,否则删的是 body 下的子节点,你却只观察了水印父容器,就收不到通知
监听目标要设在 document.body,不能是水印自己
很多人写 observer.observe(watermarkEl, { childList: true }),结果删了水印完全没反应——因为节点都不存在了,还怎么 observe?必须把观察起点设得足够高,且稳定存在。
- 正确做法:始终用
document.body作为观察目标,配合subtree: true - 在回调里靠
mutation.removedNodes判断是不是你的水印被删了,而不是靠mutation.target等于水印元素(删除时 target 是其父节点) - 注意
removedNodes是NodeList,要遍历或取[0],但别假设它一定非空;加if (node === watermarkEl)才安全
克隆节点必须用 cloneNode(true),且保留所有关键样式
水印被删后重建,如果只是 document.createElement('div') 再 setAttribute,会丢掉 inline style、z-index、pointer-events 等关键属性,导致新水印被遮挡或可点击。
- 初始创建水印后,立刻执行
const backup = watermarkEl.cloneNode(true)存为常量,后续全靠它克隆 - 不要在回调里重新计算
top/left,固定位置水印直接复用备份节点的style.cssText - 务必保留
style.pointerEvents = 'none',否则用户无法点击水印下方内容 - 如果用了 canvas 绘制水印,
cloneNode(true)不会复制 canvas 内容,此时需单独保存绘制逻辑并重放
防删 ≠ 防改,属性篡改也要同步拦截
删节点只是最粗暴的方式。开发者在控制台里右键水印 → “Edit as HTML”,把 opacity: 0.1 改成 0,或者加 display: none,水印照样消失。
- 必须开启
attributes: true,并在回调里检查mutation.type === 'attributes'且mutation.target === watermarkEl - 一旦发现
style属性被改,立刻用备份节点的style.cssText覆盖回去 - 不要只监听
class或id,攻击者可能直接删 class 再加!important规则 - 注意
oldValue在attribute类型下才有效,可用于比对是否真被改过
真正难的不是监听,是判断“什么算被篡改”——比如用户缩放页面导致水印偏移,这是正常行为;但 style.opacity 从 0.1 变成 0,就是明确信号。防删机制越细,越容易误伤;越粗,越容易被绕过。平衡点在于:只恢复你主动设置过的那几项关键样式,其余一概不管。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











