真正可用的视频防投屏水印必须用 canvas 在播放器 UI 层动态绘制并绑定会话上下文,因伪元素和静态 DOM 水印在全屏、投屏、录屏时会被浏览器渲染管线剥离;需 fixed 定位、适配 devicePixelRatio、监听 resize/loadedmetadata、处理 stacking context,并动态注入脱敏用户标识与实时时间戳。

HTML 视频防投屏水印不能靠纯 CSS 或静态背景图实现——用户一暂停、一全屏、一录屏,水印就消失或错位。真正可用的方案是用 canvas 在视频播放器 UI 层动态绘制浮动水印,并绑定当前会话上下文,让每次播放都生成带用户标识的唯一水印。
为什么 的伪元素水印根本无效
很多人试图给 元素加 ::after 伪类或绝对定位 div,结果发现:全屏后水印消失、iOS Safari 不渲染、录屏时水印被裁掉、甚至播放器控件(如 antd-video、video.js)的 z-index 直接盖住它。根本原因是:伪元素和普通 DOM 水印层不参与视频纹理合成,浏览器对 的全屏/投屏路径做了独立渲染管线,外部 DOM 层会被剥离或忽略。
常见错误现象包括:
-
Uncaught TypeError: Cannot read property 'getContext' of null—— canvas 元素没挂载到 DOM 就调用 getContext - 水印只在初始视口出现,滚动后大片空白 —— 用了 relative 定位而非
position: fixed - 文字模糊发虚 —— 没乘
window.devicePixelRatio设置 canvas 实际宽高 - 用户登录态变更后水印没更新 —— 水印内容写死,未监听
storage或 token 变化
用 canvas + video overlay 实现可投屏水印
核心是把水印画在 video 元素上方的固定定位 canvas 层里,且该 canvas 必须与 video 同宽高、同生命周期。不是“给 video 加水印”,而是“在 video 上面盖一层 canvas 水印罩”。
实操要点:
- canvas 容器需设
pointer-events: none,否则会拦截点击事件,导致播放器按钮失灵 - canvas 的
width和height必须显式设为 video 元素的 clientWidth/clientHeight ×window.devicePixelRatio,否则高分屏下文字糊成一片 - 绘制前先
ctx.translate(x, y)再rotate(),否则旋转中心偏移,文字飞出可视区 - 水印文本建议用
ctx.fillText(`USER-${userId} ${new Date().toLocaleTimeString()}`, x, y),避免静态字符串被批量替换 - 监听
resize和loadedmetadata事件重绘 canvas,但不要监听timeupdate—— 频繁重绘拖慢性能
z-index 和 stacking context 导致水印被遮挡
即使 canvas 正确绘制,也常因层叠上下文失效:比如父容器用了 transform、opacity 、will-change,或页面嵌了 iframe,都会创建新 stacking context,使你的 z-index: 9999 失效。
排查方法:
- 打开开发者工具 → Elements 面板 → 选中水印 canvas → 查看 Computed 样式中
z-index是否生效(若显示auto,说明父级创建了新 stacking context) - 检查是否被
.ant-modal、.el-dialog等 UI 库弹窗覆盖,它们默认z-index: 2000+,需手动设更高值(如z-index: 100000) - 移动端必须加
-webkit-user-select: none到 canvas 容器,否则长按触发选择框,会盖住水印文字 - iframe 内容无法被跨域水印覆盖 —— 这是浏览器安全限制,无前端绕过方案
动态绑定用户信息的关键细节
水印内容不能只取 localStorage 中的 userId,因为用户可能多标签页切换、token 过期刷新、或使用无痕模式。真正可靠的是从当前请求上下文提取:
- 优先读取已注入 window 对象的全局变量(如
window.__CURRENT_USER),由服务端 SSR 注入,防篡改性高 - 次选监听
storage事件,当其他标签页更新 token 时同步刷新水印 - 避免直接拼接明文手机号/邮箱 —— 建议哈希脱敏,如
sha256(userId).substr(0,8)(用 Web Crypto API) - 时间戳必须用
Date.now()而非服务端下发时间,防止客户端时钟被篡改 - 每 30 秒自动重绘一次水印(用
setInterval),既防静态截图,又避免高频重绘卡顿
最易被忽略的一点:水印 canvas 必须在 video 元素 loadeddata 之后才开始绘制,否则 video 尺寸未稳定,canvas 会拉伸变形;而很多框架(如 Vue 的 v-if)会延迟挂载,导致首次绘制失败。务必加一层 requestAnimationFrame 保底兜底。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











