暗水印本质是利用浏览器渲染机制在dom中嵌入人眼不可见但机器可检测的微小扰动,而非修改图片或css背景;主流方案包括伪元素+零宽字符编码、canvas像素级坐标偏移等,需满足不影响视觉、不破坏语义、抗截图录屏三大条件。

暗水印不是加在图片上,而是加在 DOM 渲染层
HTML 暗水印的本质,不是往 <img> 里塞像素,也不是改 CSS background-image,而是利用浏览器渲染机制,在文字、容器或伪元素中嵌入人眼不可见但机器可检测的微小扰动。常见错误是直接用透明文字叠在页面上——这属于“明水印伪装成暗水印”,极易被截图/OCR 清除或绕过。
真正有效的 HTML 暗水印需满足三个条件:不影响视觉、不破坏语义、能抵抗常见复制/截图/录屏操作。目前主流方案集中在 ::before/::after 伪元素 + 字符偏移 + 字体度量干扰,或利用 letter-spacing / word-spacing 的亚像素级控制做信息编码。
用 CSS 伪元素 + Unicode 零宽字符实现基础暗水印
这是最轻量、兼容性最好(IE11+)、且不依赖 JS 的方案。核心思路是:在每个块级容器末尾插入零宽字符序列(如 、、),它们不占位、不显示,但保留在 DOM 中,可被服务端或客户端脚本提取解析。
使用 PptxGenJS 和 Python 解析,将结构化的 HTML/CSS 幻灯片内容转换为原生、可编辑的 PowerPoint(.pptx)演示文稿。
- 把水印字符串转为二进制,再映射到 3 种零宽字符组合(例如 0→
, 1→, 分隔符→) - 用
::after插入,设置font-size: 0、line-height: 0、visibility: hidden,进一步消除渲染痕迹 - 避免插在
<input>或<textarea></textarea>内,这类元素不支持伪元素内容注入 - 注意:Chrome DevTools 的 Elements 面板默认会折叠零宽字符,需右键“Edit as HTML”才能看到原始码
用 Canvas 渲染文字时叠加像素级偏移水印
当页面大量使用 canvas 展示内容(如报表、图表、电子合同预览),必须在绘制阶段注入水印。此时不能靠 CSS,而要干预 ctx.fillText() 或 ctx.strokeText() 的坐标和字体参数。
- 对每个文字的
x坐标做 ±0.3px 偏移(小于 1px,人眼无法分辨,但截图后可用图像差分识别) - 偏移方向按水印 bit 位决定:0→左偏,1→右偏;用
Math.round(x + (bit ? 0.3 : -0.3))保证 canvas 不做 subpixel 抗锯齿插值 - 禁用
ctx.imageSmoothingEnabled = false,否则偏移会被模糊掉 - 不要修改
font字符串本身(比如加空格),某些 canvas 实现会重排字距,导致偏移失效
暗水印容易被绕过的三个关键点
很多方案上线后很快失效,问题不出在技术,而出在对抗逻辑没想全:
- 截图工具(如 Snipaste、ShareX)默认开启“去噪”或“边缘锐化”,会抹平亚像素偏移——得配合添加高频噪声纹理(极低透明度的 1×1 点阵 overlay)提升鲁棒性
- 用户禁用 JS 后,所有基于
document.createElement或getComputedStyle的动态水印立即消失——静态伪元素+零宽字符是唯一 fallback - PDF 导出(如 print-to-PDF)会丢弃伪元素和零宽字符——若需 PDF 水印,必须服务端生成时注入,前端暗水印仅作运行时追踪
真正难的不是“怎么加”,而是“加在哪”“加多少”“加了之后别人删不掉还察觉不到”。水印密度太高影响性能,太低又扛不住裁剪;字符偏移超过 0.5px 就可能触发浏览器重绘抖动;零宽字符在 Safari 中偶尔被自动过滤……这些细节,得实测每种目标环境才能踩准。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










