根本原因是浏览器全屏/投屏时视频被渲染到独立GPU图层,DOM水印无法叠加;必须用canvas动态适配video.clientWidth/clientHeight并绑定play/timeupdate事件实时绘制。

video全屏/投屏时CSS叠层水印失效的根本原因
直接用 position: absolute 把文字或 <img alt="HTML视频如何加入订单编号水印防投屏" > 盖在 <video></video> 上,看起来有水印,但一按F11、点全屏按钮、用OBS录屏,水印就彻底消失。这是因为:浏览器原生全屏播放器会把视频渲染到独立图层(GPU纹理),DOM元素完全脱离该图层;投屏协议(如Chromecast、AirPlay)只传输解码后的YUV帧,不传HTML布局树;pointer-events: none 只能透点击,不能透画面。
canvas实时绘制水印必须同步video.clientWidth/clientHeight
canvas尺寸必须随video显示区域动态变化,否则缩放、旋转、响应式布局下水印会拉伸、错位或被裁切。关键不是video的videoWidth/videoHeight(原始分辨率),而是当前渲染尺寸:
- 监听
resize和loadedmetadata事件,初始设 canvas.width = video.clientWidth, canvas.height = video.clientHeight - 在绘制循环中每次调用
ctx.drawImage(video, 0, 0, canvas.width, canvas.height)前,先检查并重置 canvas 尺寸 - 移动端需额外监听
orientationchange,Safari 的 video 全屏会触发 viewport 重排
订单编号水印要防截屏+防溯源篡改
单纯写死“ORDER-123456”没用——用户F12删掉JS或禁用脚本就能绕过。真要防投屏盗用,得让水印内容与当前会话强绑定:
- 从后端接口异步获取订单ID,带签名(如
order_id=123456&sig=sha256(user_id+ts+secret)),避免前端硬编码 - 水印文字建议右下角锚定:
ctx.fillText(orderId, canvas.width - 20, canvas.height - 20),避免旋转后跑出画布 - 透明度用
ctx.globalAlpha = 0.25,别用style.opacity,后者会让整个canvas变淡 - 加节流:订单ID不变时,300ms内不重复调用
fillText,减少CPU占用
requestAnimationFrame(draw) 必须绑定 play/timeupdate,不能只靠 setInterval
用 setInterval 每50ms刷一次,会浪费资源且不同步视频帧;只监听 play 不够,拖动进度条或跳转时 timeupdate 才触发新帧。正确做法是:
立即学习“前端免费学习笔记(深入)”;
video.addEventListener('play', () => requestAnimationFrame(draw))video.addEventListener('timeupdate', () => requestAnimationFrame(draw))- draw 函数开头必须加
if (!video.paused && !video.ended)判断,避免暂停时白屏或报错 - 每次绘制前务必
ctx.clearRect(0, 0, canvas.width, canvas.height),否则上一帧残留导致文字重影
真实场景里最容易被忽略的是:canvas尺寸未随video显示区域实时更新,以及水印文本未做服务端动态注入。这两点一漏,所谓“防投屏”就只剩心理安慰。



















