页面水印怎样实现?怎样防止被轻易删除?
先理解原理和示例,再练习完整口述与追问。
回答要点
明水印:用 Canvas 或 SVG 绘制包含用户名、时间的旋转文字,转成图片作为全屏覆盖层的重复背景,设置 pointer-events: none 不影响操作。防删除:MutationObserver 监听水印节点被删除或样式被修改时重新生成。前端水印只能增加删除成本,追溯泄露要依靠服务端生成的图片水印或暗水印。
原理拆解
水印层固定定位、覆盖全屏、z-index 足够高,pointer-events: none 让点击穿透到下面的内容。Canvas 绘制后 toDataURL 得到 Base64 图片,作为 background-image 平铺。MutationObserver 监听 body 的子节点变化(水印被删除)和水印节点的属性变化(样式被改为隐藏),检测到后恢复;对于 Shadow DOM 中的水印,用户更难通过元素面板修改。
用户仍可通过禁用 JS、开发者工具删除观察者、截图后修图等方式去除水印,因此重要文件应由服务端在导出或预览时把水印合成进图片或 PDF;暗水印通过微调像素或频域信息嵌入标识,肉眼不可见但可提取。
示例与推演
function createWatermark(text) {
const canvas = Object.assign(document.createElement('canvas'), { width: 240, height: 160 })
const ctx = canvas.getContext('2d')
ctx.rotate(-Math.PI / 9)
ctx.font = '14px sans-serif'
ctx.fillStyle = 'rgba(0, 0, 0, 0.12)'
ctx.fillText(text, 10, 110)
const style = `position:fixed;inset:0;z-index:9999;pointer-events:none;background:url(${canvas.toDataURL()}) repeat`
const layer = document.createElement('div')
layer.style.cssText = style
document.body.appendChild(layer)
const observer = new MutationObserver(() => {
if (!document.body.contains(layer)) document.body.appendChild(layer)
if (layer.style.cssText !== style) layer.style.cssText = style
})
observer.observe(document.body, { childList: true })
observer.observe(layer, { attributes: true, attributeFilter: ['style', 'class', 'hidden'] })
return () => { observer.disconnect(); layer.remove() }
}
恢复操作本身也会触发观察者,回调中先比较再修改,避免无限循环。
易错点
水印文字应包含用户标识以便追溯,但不要放手机号等完整隐私数据;打印时浏览器可能不打印背景,需要 print-color-adjust: exact。
参考回答可直接练习口述
我会用 Canvas 画带用户名和时间的倾斜文字,转成图片作为一个全屏固定层的平铺背景,pointer-events: none 不影响操作。防删除用 MutationObserver 监听 body 子节点和水印节点的样式变化,被删或被改就恢复,恢复前先比较避免循环。但前端水印只能提高删除成本,真正需要追溯的文件应该由服务端在图片或 PDF 中合成水印,或使用暗水印。