Canvas动态绘制水印更可靠,因其是实时重绘的不可选像素层,能应对禁用CSS、截图、打印等场景;而background-image属样式层,易错位或消失。

Canvas 动态绘制水印为什么比 background-image 更可靠
因为 background-image 是纯样式层,禁用 CSS、截图、打印、缩放或局部滚动都会导致水印错位、消失或不可见;而 Canvas 绘制的水印是实时重绘的像素层,天然不可选、不可右键保存、不随 DOM 删除而消失。关键不是“画一次”,而是监听 resize 和 scroll(需配合节流)持续重绘全屏网格。
实操建议:
- 用
devicePixelRatio缩放 canvas 画布尺寸,避免高清屏模糊 -
ctx.fillText()文字必须用固定单位(如24px),别用rem或vw,否则缩放时文字大小突变 - 旋转角度控制在
-25到-35度之间,太小看不出斜纹,太大干扰阅读 - 透明度设为
0.08–0.12,再低肉眼难辨,再高影响内容可读性 - 网格间距(
spacing)建议100–150px,太密性能差,太疏中间留白
用 body::before 伪元素实现轻量水印的兼容边界
伪元素方案适合快速上线、兼容老浏览器(IE9+)、不依赖 JS 的场景,但它的“防删”能力仅限于用户不主动删样式——一旦打开 DevTools 禁用 ::before 规则,水印立刻归零。
常见错误现象:
立即学习“前端免费学习笔记(深入)”;
- 用
position: absolute导致水印被局部滚动容器截断 -
content值为空或没加引号(如写成content: 机密而非content: "机密"),水印根本不渲染 - 字体太小(
<32px)在 Retina 屏上糊成灰块 - 漏写
pointer-events: none,导致按钮/输入框无法点击
推荐写法:body::before { content: "内部使用"; position: fixed; top: 0; left: 0; width: 100%; height: 100%; transform: rotate(-30deg); font-size: 48px; color: rgba(0,0,0,0.08); z-index: 9999; pointer-events: none; }
防删除的核心不是“藏”,而是“复活”
DOM 可删、样式可关、canvas 元素可移除——所有静态节点都逃不过用户手动干预。真正起作用的是监听机制:检测水印 DOM 是否被移除、getComputedStyle 是否返回 display: none 或 opacity: 0,并在几毫秒内自动重建。
实操要点:
- 不要只监听
MutationObserver的子节点删除,还要轮询检查offsetParent === null或getComputedStyle(el).display === 'none' - 重建逻辑必须幂等,避免重复插入多个 canvas 或伪元素
- 节流重绘频率(如
requestIdleCallback或setTimeout(..., 16)),否则拖慢页面响应 - 移动端需额外监听
orientationchange,横竖屏切换会重置window.innerWidth
水印内容动态注入与敏感信息隔离
硬编码水印文字(如 "机密-禁止转载")在多环境部署时容易出错。更稳妥的做法是把水印文案从后端接口或环境变量中注入,比如通过 data-watermark-text 属性读取,或初始化时调用 fetch('/api/watermark')。
注意点:
- 避免在水印里暴露用户 ID 或 token 等敏感字段,防止截图泄露身份信息
- 若需溯源,优先用不可逆哈希(如
sha256(userId + timestamp))生成简短标识,而非明文 - 测试环境水印建议带
[TEST]前缀,生产环境用[PROD],靠 class 名或 data 属性区分样式 - 服务端渲染(SSR)页面需在客户端首次绘制前就注入水印,否则首屏无水印,存在时间窗口



















