HTML 的 `<track>` 元素给视频加字幕?WebVTT 怎么用
给视频加字幕的标准做法是:<video> 里放一个 <track> 子元素,src 指向一个 UTF-8 编码的 .vtt(WebVTT)文件,同时写上 kind 和 srclang;字幕不显示,90% 是服务器 MIME 类型没配成 text/vtt、文件路径/跨域不对,或 VTT 首行不是 WEBVTT。
<track> 怎么给视频加字幕?最小可用代码
结论:一个 <track> 对应一路字幕,多语言就写多个,default 只加一个。
<video controls width="640" crossorigin="anonymous">
<source src="movie.mp4" type="video/mp4">
<track kind="subtitles" src="subs/zh.vtt" srclang="zh" label="中文" default>
<track kind="subtitles" src="subs/en.vtt" srclang="en" label="English">
</video>
<track> 是空元素(无闭合标签),必须是 video/audio 的子元素,不能脱离媒体元素单独存在。label 是给用户看的名字,会出现在浏览器自带控件的字幕菜单里;srclang 是字幕语言代码,kind="subtitles" 时属于必填。如果 VTT 和页面不同源,视频标签要加 crossorigin="anonymous",并且服务器返回 Access-Control-Allow-Origin,否则字轨会被 CORS 拦掉。
WebVTT 文件怎么写?
结论:首行必须是 WEBVTT,然后空一行,再用「时间戳 --> 时间戳 + 文本」结构写每条 cue。
WEBVTT
00:00:01.000 --> 00:00:04.000
欢迎来到前端性能专栏
00:00:04.500 --> 00:00:08.000
第一件事:把 LCP 压到 2.5 秒以内
时间戳有两种写法:mm:ss.mmm 和 hh:mm:ss.mmm,毫秒必须 3 位,分隔符用点号(.)而不是逗号——逗号是 SRT 的写法,直接改后缀名把 .srt 变 .vtt 是最常见的翻车点。文本内可用 <b> <i> <u> <ruby> 和 <v 说话人> 标签。文件必须存成无 BOM 的 UTF-8。
Web 服务器要认识 .vtt。Nginx 里加一行:
types { text/vtt vtt; }
MIME 类型不对时,Chrome 会直接忽略这个 track,控制台还不一定报错,这是最难查的一类问题。
kind 的 5 个值有什么区别?
结论:普通翻译字幕用 subtitles,只有需要描述音效(如「[门砰地关上]」)给听障用户时才用 captions。
| kind | 用途 | srclang | 典型场景 |
|---|---|---|---|
| subtitles | 语言翻译字幕 | 必填 | 中文用户看英文视频 |
| captions | 含音效/说话人 | 必填 | 静音播放、听障用户 |
| descriptions | 画面语音描述 | 必填 | 视障用户 |
| chapters | 章节导航 | 可选 | 进度条切章节 |
| metadata | 供脚本读取 | 可选 | 弹幕、广告位 |
读表结论:前三种影响渲染,后两种浏览器不渲染文本,只把 cue 交给 JS。
用 JS 控制字幕:textTracks API
结论:video.textTracks 是一个 TextTrackList,通过 mode 控制显隐,cuechange 事件拿到当前字幕。
const track = video.textTracks[0];
track.mode = 'showing'; // disabled | hidden | showing
track.addEventListener('cuechange', () => {
const cue = track.activeCues[0];
if (cue) console.log(cue.text, cue.startTime, cue.endTime);
});
disabled 完全不加载、不触发事件;hidden 加载并触发 cuechange 但不渲染——想用自定义 DOM 画字幕就用 hidden。做双语字幕时,把两路 track 都设成 hidden,自己拼到页面上,还能顺便控制排版。
字幕样式怎么改?
结论:用 ::cue 伪元素改内置渲染的字幕样式,但跨域字轨会被限制。
video::cue { background: rgba(0,0,0,.75); color: #fff; font-size: 20px; }
video::cue(v[voice="讲师"]) { color: #4ade80; }
::cue 只作用于浏览器原生字幕框,不能改位置布局(位置由 cue 的 line、position、align 设置控制)。想要圆角、阴影、动画这类效果,只能走 hidden + 自定义 DOM 的路子。
常见误区
❌ 把 .srt 直接改名为 .vtt → ✅ 重写时间戳分隔符为点号,并确保首行是 WEBVTT
❌ 只写 src 不写 srclang → ✅ subtitles/captions/descriptions 必须带 srclang
❌ 服务器返回 text/plain → ✅ Nginx 配置 text/vtt vtt;,或后端显式设置 MIME
❌ 跨域 VTT 不写 crossorigin → ✅ 加 crossorigin="anonymous" 并配置 CORS 响应头
❌ VTT 文件带 UTF-8 BOM → ✅ 保存为无 BOM 的 UTF-8,否则首行 WEBVTT 解析失败
❌ 给多个 track 都加 default → ✅ 只保留一个,否则以最后一个为准(浏览器行为不一致)
自查清单
- [ ]
<track>是video/audio的直接子元素,且为自闭合写法 - [ ]
kind选了正确的值,普通翻译用subtitles - [ ]
srclang是合法的 BCP 47 语言代码(如zh、en、zh-Hans) - [ ]
label有可读名称,会显示在原生字幕菜单 - [ ]
default只出现在一路 track 上 - [ ]
.vtt首行是WEBVTT,第二行为空行 - [ ] 时间戳用点号分隔毫秒,且毫秒为 3 位
- [ ] 文件是无 BOM 的 UTF-8 编码
- [ ] 服务器返回
Content-Type: text/vtt - [ ] 跨域场景已加
crossorigin="anonymous"且 CORS 头生效 - [ ] 用
track.mode = 'showing'或cuechange验证过 JS 能读到 cue
<track> 本身不复杂,坑集中在三处:VTT 文件格式(点号毫秒、首行、编码)、服务器 MIME 类型、跨域。把这三处都过了,多语言字幕、cuechange 驱动的自定义字幕渲染、::cue 样式都能顺下来;再想深度定制,就切到 hidden 模式自己画。
原文链接:https://www.gj0.com/thread-1253.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。