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

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











