如何检测水印DOM元素的变更?含开发者工具修改场景
检测水印DOM变更与样式篡改的解决方案
针对你遇到的水印检测需求,以下是几个可行的技术方案,覆盖DOM移除、样式属性修改(包括开发者工具操作)的场景:
一、正确配置MutationObserver(核心方案)
你之前提到MutationObserver只能检测代码修改是配置不当导致的——它完全可以监听开发者工具对DOM和样式的修改。关键要同时监听元素移除和样式属性变化:
具体实现步骤
- 监听水印元素的父节点,捕获水印被移除/替换的事件
- 监听水印元素自身的
style属性变更,同时可扩展监听class属性(防止通过类名修改样式) - 通过
getComputedStyle获取实际生效的样式,判断是否被篡改
const watermark = document.getElementById('watermark'); const parentNode = watermark.parentNode; // 配置监听规则:监听样式属性、类名变化,以及父节点的子元素增删 const elementObserverConfig = { attributes: true, attributeFilter: ['style', 'class'], // 只监听目标属性,提升性能 attributeOldValue: true // 保留旧值,方便对比变更内容 }; const parentObserverConfig = { childList: true, subtree: false // 仅监听父节点直接子元素,无需深入子树 }; const observer = new MutationObserver((mutations) => { mutations.forEach(mutation => { // 处理水印被移除的情况 if (mutation.type === 'childList') { if (!document.contains(watermark)) { console.log('水印已被从DOM中移除'); // 这里执行恢复逻辑,比如重新创建并插入水印 restoreWatermark(); } } // 处理样式/类名篡改 else if (mutation.type === 'attributes') { const computedStyle = window.getComputedStyle(watermark); const isHidden = computedStyle.opacity === '0' || computedStyle.display === 'none' || computedStyle.visibility === 'hidden'; if (isHidden) { console.log('水印样式被篡改,已隐藏'); // 强制恢复水印样式 Object.assign(watermark.style, { opacity: '1', display: 'block', visibility: 'visible' }); } } }); }); // 启动监听:同时监听父节点(检测移除)和水印元素(检测样式) observer.observe(parentNode, parentObserverConfig); observer.observe(watermark, elementObserverConfig); // 示例:恢复水印的函数 function restoreWatermark() { const newWatermark = document.createElement('div'); newWatermark.id = 'watermark'; // 填充水印的样式和内容 newWatermark.style.cssText = 'position:absolute; top:0; left:0; ...'; newWatermark.textContent = '版权所有'; parentNode.appendChild(newWatermark); // 重新绑定监听 observer.observe(newWatermark, elementObserverConfig); }
二、结合WeakRef与setInterval做兜底检测
如果担心MutationObserver在某些极端场景下失效(比如开发者工具快速删除并替换元素),可以用WeakRef避免内存泄漏,配合定时检测做兜底:
const watermarkRef = new WeakRef(document.getElementById('watermark')); const checkInterval = setInterval(() => { const watermark = watermarkRef.deref(); if (!watermark) { console.log('水印已被移除'); clearInterval(checkInterval); restoreWatermark(); return; } const computedStyle = window.getComputedStyle(watermark); const isHidden = computedStyle.opacity === '0' || computedStyle.display === 'none' || computedStyle.visibility === 'hidden'; if (isHidden) { console.log('水印样式被篡改'); Object.assign(watermark.style, { opacity: '1', display: 'block', visibility: 'visible' }); } }, 500); // 检测间隔可根据需求调整,建议500-1000ms
这里WeakRef的作用是:当水印元素被DOM移除后,不会阻止浏览器垃圾回收该元素,避免内存泄漏。
三、额外防御:监听祖先元素样式(可选)
如果攻击者通过修改水印的父元素样式来隐藏水印(比如给父元素加display:none),可以扩展监听父元素甚至更高层级祖先的样式变化,或者在检测时遍历祖先元素的计算样式:
// 示例:检查所有祖先元素是否被隐藏 function isAncestorHidden(element) { let current = element.parentNode; while (current && current !== document.body) { const style = window.getComputedStyle(current); if (style.display === 'none' || style.visibility === 'hidden') { return true; } current = current.parentNode; } return false; } // 在MutationObserver或setInterval中调用 if (isAncestorHidden(watermark)) { console.log('水印被祖先元素隐藏'); // 恢复祖先元素样式或调整水印层级 }
注意事项
- 无法完全阻止技术人员篡改:开发者工具完全可控,上述方案只能增加篡改成本,无法做到100%防御
- 性能平衡:MutationObserver是异步触发,性能优于setInterval;定时检测的间隔不要太小,避免影响页面性能
- 恢复逻辑要健壮:重新创建水印时,要确保不会重复添加,且样式、层级与原水印一致
内容的提问来源于stack exchange,提问作者prettyInPink
相关产品推荐
相关产品推荐

