不能靠单纯提高 z-index 让普通元素盖住 video,因其原生控件运行在浏览器 ui 层,高于 css 渲染层;需用 position: relative 的外层容器包裹 video,并避免触发隐式层叠上下文,同时禁用原生 controls 或手写控件。

不能靠单纯提高 z-index 就让普通元素盖住 video —— 因为原生控件(尤其是带 controls 的)运行在浏览器 UI 层,天然高于 CSS 渲染层。
video 默认不创建定位上下文,relative 必须加在外层容器上
直接对 <video></video> 标签设 position: relative 是无效的。它默认是 inline 元素,且多数浏览器不将其视为可靠的定位参考。
- 必须用一个
<div class="video-wrapper"> 包裹 <code><video></video> - 对这个
<div> 显式设置 <code>position: relative - 确保该容器宽高与视频一致(推荐用
width: 100%+aspect-ratio或固定尺寸) - 避免用
display: flex或grid的父容器“替代”position: relative—— 它们不自动创建定位上下文 - 遮罩层和
<video></video>应该是兄弟节点,且共同父级是那个position: relative的 wrapper - 或者更稳妥:遮罩层直接作为 wrapper 的子元素,
position: absolute覆盖整个区域 - wrapper 上禁止出现
transform、filter、opacity: 0.99等隐式创建 stacking context 的属性 - 遮罩层自身必须设
position: absolute(不是fixed),否则滚动或缩放时会错位 - 彻底禁用原生控件:
<video controls="false"></video>或干脆不写controls属性 - 手写播放/暂停/音量等 DOM 控件,并用 JS 绑定行为(iOS 要求用户手势触发
play()) - 若必须保留原生控件,可尝试加
playsinline+webkit-playsinline+transform: translateZ(0)强制提升图层,但兼容性差,不推荐用于生产 - 进度条优先用
<input type="range">,比手动画 div 更可靠,触控响应好,无障碍支持完善 - video 标签至少应包含:
playsinline webkit-playsinline x5-video-player-fullscreen="true" x5-video-orientation="portrait" -
autoplay和muted="true"必须同时存在,否则 Chrome/Safari 会静音卡首帧或报DOMException -
preload="metadata"不可省略,否则滑到页面时视频还没加载,控件状态不同步
覆盖层必须同级或包裹 video,且不能意外触发层叠上下文
遮罩层如果放在 <video></video> 外部、但父容器有 transform / filter / opacity ,就会创建新的 stacking context,导致 <code>z-index 在内部重新计数,失效。
原生 controls 是穿透根源,移除它是最稳解法
只要 <video controls></video> 存在,哪怕遮罩层 z-index: 9999、pointer-events: none,进度条/全屏按钮仍可能被点击穿透,尤其在安卓 WebView 和旧版 Safari 中。
移动端必须加 playsinline 和 x5 属性才能保底显示
iOS Safari 不加 playsinline 会强制全屏并接管 UI;微信/QQ 内嵌 WebView 还需要 x5-video-player-fullscreen="true" 等腾讯系属性,否则自定义控件直接不可见。
真正难的不是写几行 CSS,而是意识到:video 不是普通 HTML 元素,它的渲染层级由浏览器内核决定。所有“覆盖失败”的现象,几乎都源于试图绕过这一规则——而不是你的 z-index 不够大。











