纯前端无法真正防截屏,仅能通过动态canvas水印增加截图干扰:倾斜15°、透明度0.08、随机偏移±20px、每300ms重绘,并适配devicePixelRatio。

HTML防截屏水印本质是“视觉干扰”,不是技术封锁
直接说结论:纯前端 HTML/CSS/JS 无法真正阻止截图,所谓“防截屏”只是通过动态覆盖、高频率扰动、DOM 干扰等方式提高截图成本和可用性——截图依然能成功,但截出来的图带明显水印、文字错位或闪烁,难以用于盗用。别信“100%防截屏”的宣传,那是误导。
用 canvas 叠加实时动态水印最可控
比 CSS 伪元素或固定背景图更难绕过:canvas 内容可编程生成(含用户 ID、时间戳)、支持透明度渐变、坐标随机偏移,且能响应 resize 和 scroll 重绘。关键点:
- 水印文字必须倾斜(
rotate(15))、低透明(globalAlpha = 0.08)、大间距(避免连成可读块) - 每个水印块位置用
Math.random()偏移 ±20px,防止被脚本批量清除 DOM 节点 - 绘制频率控制在每 300ms 重绘一次,太密卡顿,太疏易被静帧捕获
- 务必监听
window.devicePixelRatio,否则高清屏下水印模糊或过细
const drawWatermark = () => {
const canvas = document.getElementById('watermark-canvas');
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.globalAlpha = 0.08;
ctx.font = '16px Microsoft YaHei';
ctx.fillStyle = '#000';
ctx.rotate(-Math.PI / 12);
for (let i = 0; i < 30; i++) {
const x = Math.random() * window.innerWidth + 50;
const y = Math.random() * window.innerHeight + 100;
ctx.fillText(`ID:${userId} ${new Date().toLocaleTimeString()}`, x, y);
}
};
pointer-events: none 和 z-index 配合不当会导致水印失效
常见错误:把水印层设为 position: fixed 但没加 pointer-events: none,结果用户点不到底下的按钮;或者 z-index 被其他组件(如 antd 的 .ant-modal)覆盖,水印直接看不见。实操要点:
- 水印容器必须设
pointer-events: none,内部所有子元素也需显式继承(* { pointer-events: none; }) -
z-index值建议 ≥ 9999,且检查是否被transform或iframe创建新 stacking context 覆盖 - 若页面有
iframe,水印无法跨域覆盖其内容——这是浏览器安全限制,无解 - 移动端要额外加
-webkit-user-select: none,防止长按触发文字选择框遮挡水印
服务端注入水印文本比前端拼接更安全
前端硬编码水印内容(如 document.getElementById('user-id').innerText)容易被 DevTools 修改或绕过。推荐做法:
立即学习“前端免费学习笔记(深入)”;
- 后端渲染时将加密后的用户标识写入
data-watermark属性:<body data-watermark="a2V5OjE2MjQyNTAwMDAwMDAw"> - 前端 JS 解密该值(简单 Base64 或 AES-CBC,密钥由后端动态下发),再喂给 canvas 绘制逻辑
- 避免在 JS 中明文出现敏感字段(如邮箱、手机号),哪怕只是水印用途
- 注意 SSR 场景下首次渲染时 canvas 可能未挂载,需用
requestAnimationFrame延迟绘制
真正难防的是录屏+OCR 提取文字,以及用户用另一台设备拍照。动态水印只抬高第一道门槛,别把它当保险柜用。



















