video元素不能直接transition opacity,因其会强制退出GPU合成层,导致解码中断、黑屏、音画不同步;必须用双实例+visibility+opacity分层控制,并确保loadeddata触发、帧同步及移动端兼容处理。

直接给 <video> 元素加 opacity + transition 无法实现真正平滑的淡入——它会中断解码、触发重载、造成黑屏或音频撕裂。必须用双实例 + visibility + opacity 分层控制,且预加载和帧同步缺一不可。
为什么 video 元素不能直接 transition opacity
浏览器对 <video> 的硬件加速路径和合成层管理高度敏感。opacity 过渡会强制其退出 GPU 合成层,导致播放管线被重置:已缓冲帧丢弃、音频解码中断、首帧延迟渲染。常见表现是淡入开始瞬间黑屏 0.3–0.8 秒,或音画不同步。
-
display: none或visibility: hidden会彻底终止解码器,再 show 时等同于新加载 -
transform: translateZ(0)对 video 无效,无法靠它“抢救”过渡 - 即使加了
preload="auto",若未监听loadeddata就触发淡入,仍会闪白/黑
双 video 实例 + visibility 控制切换流程
核心是让两个 <video> 始终在 DOM 中,仅通过视觉层切换显隐。旧视频用 visibility: hidden(保留解码状态),新视频用 opacity 过渡入场。
- 两个
<video>必须同级、position: absolute、尺寸完全一致,避免错位 - 新视频需监听
loadeddata(非canplay),确保首帧已解码完成才开始过渡 - CSS 过渡只写在新视频上:
transition: opacity 0.6s ease-out;旧视频不加 transition - 切前设新视频
opacity: 0; visibility: visible,切时改opacity: 1,切后立即将旧视频设为visibility: hidden
移动端与 Safari 必须处理的细节
iOS Safari 对自动播放限制极严,且对 preload 和 poster 行为特殊,不处理就会静音失败或首帧卡顿。
立即学习“前端免费学习笔记(深入)”;
- 两个
<video>都必须带muted属性,否则 autoplay 在 iOS 上直接被忽略 -
poster必须指向一张首帧截图(推荐.webp),防止loadeddata前出现黑屏 - Safari 要求
preload="auto"显式声明,preload="metadata"不够用 - 避免在
<video>上叠加filter(如blur()),某些版本 Chrome 会强制切回 CPU 渲染,放大卡顿
进阶:requestVideoFrameCallback 精准帧同步
当需要多视频时间轴对齐(比如轮播+字幕同步)、或消除 CSS transition 定时器带来的微卡顿时,transition 的毫秒级精度就不够用了。它不与视频帧率绑定,容易跳帧。
- 用
requestVideoFrameCallback替代setTimeout触发淡入,确保动画起始点严格落在视频帧边界上 - 仅在新视频触发
loadeddata后注册回调,避免提前调用 - 该 API 目前仅 Chromium 和 Safari 支持,Firefox 需降级回
loadeddata+setTimeout(..., 16)
最容易被忽略的是:过渡结束 ≠ 切换完成。旧视频的 visibility: hidden 必须在新视频 opacity 到达 1 后立即执行,中间不能有 JS 执行间隙;否则人眼会捕捉到两层同时可见的叠影。这个时机差,比动画时长本身更关键。

















