直接修改 track.src 不生效,因浏览器仅在加载阶段解析并缓存 src;正确做法是预加载所有语言的 track 或动态创建新 track 并监听 load 事件或检查 readyState 为 2 后再设 mode。

直接改 track.src 不生效,浏览器只读一次
很多人以为给已存在的 <track></track> 元素动态设置 src 属性就能换字幕,比如 trackEl.src = "ja.vtt"——这完全没用。浏览器在 <video></video> 加载阶段解析并缓存 <track></track> 的 src,后续修改该属性不会触发重新加载或解析,也不会报错,字幕就一直卡在旧内容上。
真正起作用的前提是:新字幕文件必须已通过 HTML 声明(即页面加载时就存在对应的 <track></track> 标签),或者你手动创建并插入了新的 <track></track> 元素,且等待其加载完成。
- ✅ 正确做法:预加载所有语言的
<track></track>,用 JS 切换mode - ❌ 错误做法:只写一个
<track></track>,然后反复改它的src和srclang - ⚠️ 注意:
track.readyState必须为2(TextTrack.LOADED)才能安全设mode = "showing",否则静默失败
动态添加 <track></track> 后必须等 load 事件
如果字幕内容是运行时生成的(比如从 API 拉取翻译后拼成 WebVTT 字符串),你得先创建 <track></track> 元素、注入 src(注意:不能是 Blob URL)、append 到 <video></video> 内部,再监听加载完成。
关键点不是“加进去”,而是“等它真加载完”。常见错误是加完立刻设 mode,结果字幕不显示。
立即学习“前端免费学习笔记(深入)”;
- 用
fetch获取 VTT 文本 → 写入临时文件或用URL.createObjectURL(new Blob([vttText], {type: "text/vtt"}))?不行,<track></track>不支持 Blob URL,必须用真实路径 - 推荐方案:把动态生成的 VTT 存到内存中,用
document.createElement("track")创建新元素,track.kind = "subtitles",track.srclang = "zh",track.label = "中文",然后video.appendChild(track) - 接着监听
track.addEventListener("load", () => { track.mode = "showing"; }),或轮询track.readyState === 2
不用 <track></track> 的纯 JS 字幕层怎么防抖和对齐
当 <track></track> 不可控(比如移动端 Safari 不响应、或需要双语叠加、或 VTT 来源不可信),就得自己用 <div id="subtitle"> 渲染。但这不是简单地 <code>textContent = xxx 就完事。
核心难点是时间同步精度和视觉稳定性:播放器 timeupdate 事件每秒触发多次但不均匀,字幕 cue 时间范围有重叠、跳帧、或渲染延迟,直接查表赋值会导致闪烁或串句。
- 必须做防抖:用
requestAnimationFrame节流更新,而不是在timeupdate里直接改 DOM - 查表逻辑要支持“当前时间落在哪个 cue 区间内”,不能只比开始时间;需同时判断
currentTime >= start && currentTime - WebVTT 解析不能靠正则硬切:需识别
WEBVTT头、跳过注释行、处理NOTE、兼容X-TIMESTAMP-MAP(如有) -
::cue样式不生效?那是用了自定义层——得手动继承字体、行高、颜色,且font-size: 1.2em在不同设备缩放下表现不一,建议用rem或固定px
移动端 fallback 必须检测 textTracks.length === 0
iOS Safari 默认隐藏字幕控件,旧版 Android WebView 甚至不暴露 textTracks 列表。别依赖 video.textTracks 总是有东西,更别信 default 属性在移动 Safari 上能自动生效。
真实场景中,你得主动探测能力边界,再决定走原生轨道还是降级方案。
- 先检查
if (video.textTracks.length === 0 || !("mode" in video.textTracks[0])),满足任一条件就启用自定义字幕层 - 不要用
video.subtitleEnabled——这个属性根本不存在,MDN 都没记 - 自定义 UI 开关按钮必须显式控制
track.mode,而不是只改按钮样式;用户点了“中文”,就要遍历所有track,把srclang === "zh"的设为"showing",其余设为"disabled" - 即使走自定义层,也要保留原始
<track></track>标签(哪怕不启用),方便无障碍阅读器抓取文本



















