第一章:HTML video 元素基础

你以为 <video src="xxx.mp4" controls> 就够了?那你可能踩过、或者正在踩很多坑。这篇文章带你系统梳理 <video> 的每一个属性,讲清楚它背后的机制,配上真实场景的代码,让你真正掌握这个看似简单、实则深藏玄机的标签。


为什么要深入理解 <video>

2008 年 HTML5 草案引入 <video> 标签,彻底终结了 Flash 时代。但这个标签远比你想象的复杂——它不只是一个"播放器容器",它是一个拥有完整状态机、事件系统、API 接口的媒体引擎入口。

很多开发者对它的理解停留在"能播就行"的阶段,直到在移动端遇到自动播放失效、视频全屏被劫持、进度条卡在某个位置、Canvas 截图报跨域错误……这些问题的根源,几乎都能追溯到对某个属性理解不到位。


第一部分:最小可用结构

先从最基础的写法开始,然后一步步拆解每个属性的真实含义。

<video
  src="https://example.com/video.mp4"
  controls
  width="800"
  height="450"
  poster="https://example.com/cover.jpg"
  preload="metadata"
>
  您的浏览器不支持 video 标签
</video>

这是一个"能用"的最小结构。但如果你要做 AI 视频、直播、自定义播放器,仅靠这几行是远远不够的。下面逐个属性深入讲解。


第二部分:媒体源属性

src — 直接指定视频地址

最直接的方式,但有一个重要限制:src 只能指定单一来源,无法做格式兜底。一旦浏览器不支持该格式,视频就播不出来,什么提示也没有。

<!-- 这样写有格式兼容风险 -->
<video src="video.webm" controls></video>

正确的做法是使用 &lt;source&gt; 子标签。


&lt;source&gt; 子标签 — 多格式兜底的正确姿势

&lt;source&gt;&lt;video&gt; 最重要的子元素,浏览器会从上到下依次检测,选择第一个它能播放的格式。

<video controls width="800" height="450">
  <!-- 优先 WebM/VP9,压缩率更高,Chrome/Firefox 优先支持 -->
  <source src="video.webm" type="video/webm; codecs='vp9, opus'" />
  <!-- 兜底 MP4/H.264,兼容性最广 -->
  <source src="video.mp4" type="video/mp4; codecs='avc1.42E01E, mp4a.40.2'" />
  <!-- 最终降级文案 -->
  <p>您的浏览器不支持 HTML5 视频,请 <a href="video.mp4">下载视频</a></p>
</video>

type 属性的 codec 字符串怎么写?

type 属性不是随便写的,它的完整格式是 MIME类型; codecs="视频编码, 音频编码"。浏览器用它来快速判断是否支持,避免下载整个文件才发现播不了。

格式type 完整写法
MP4 H.264 + AACvideo/mp4; codecs="avc1.42E01E, mp4a.40.2"
WebM VP9 + Opusvideo/webm; codecs="vp9, opus"
WebM VP8 + Vorbisvideo/webm; codecs="vp8, vorbis"
OGG Theoravideo/ogg; codecs="theora, vorbis"

实践建议:生产环境至少提供 MP4(H.264)+ WebM(VP9)两个版本,覆盖率可以达到 99%+ 的现代浏览器。


&lt;track&gt; 子标签 — 字幕与元数据轨道

&lt;track&gt;&lt;video&gt; 的另一个子元素,用于挂载外部文本轨道,支持 WebVTT(.vtt)格式。

<video controls src="video.mp4">
  <!-- default 表示默认启用这条字幕 -->
  <track
    default
    kind="subtitles"
    src="subtitles_zh.vtt"
    srclang="zh"
    label="中文"
  />
  <track
    kind="subtitles"
    src="subtitles_en.vtt"
    srclang="en"
    label="English"
  />
  <!-- 章节标记,可用于实现视频章节导航 -->
  <track kind="chapters" src="chapters.vtt" srclang="zh" />
</video>

kind 属性的五个值:

  • subtitles:字幕(翻译,不含音效描述)
  • captions:标题(含音效描述,给听障用户)
  • descriptions:视频内容的文字描述(给视障用户)
  • chapters:章节标记(可用于进度条章节导航)
  • metadata:不显示给用户,供 JS 读取的数据

一个简单的 .vtt 字幕文件长这样:

WEBVTT

00:00:01.000 --> 00:00:04.000
欢迎来到 HTML video 深度解析

00:00:05.000 --> 00:00:09.000
今天我们来聊聊那些你不知道的属性细节

跨域注意&lt;track&gt; 的字幕文件必须遵守同源策略,跨域字幕需要服务器配置 CORS 响应头 Access-Control-Allow-Origin,否则浏览器会静默拒绝加载。MDN


第三部分:呈现属性

poster — 封面图的那些坑

poster 是视频加载前或用户点击播放前显示的封面图。看起来简单,但有几个细节需要注意。

<video
  src="video.mp4"
  controls
  poster="https://example.com/cover.jpg"
></video>

坑一:poster 图片的尺寸适配poster 图片默认会按照 &lt;video&gt; 元素的尺寸进行缩放,如果图片比例与视频比例不一致,会出现黑边或拉伸。解决方案是用 CSS object-fit 控制:

video {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover; /* 或 contain,根据需求选择 */
}

坑二:动态生成封面。很多情况下后端没有提供封面图,需要前端从视频第一帧截取。正确方案是用 Canvas:

function getVideoPoster(videoSrc) {
  return new Promise((resolve) => {
    const video = document.createElement('video');
    video.crossOrigin = 'anonymous'; // 跨域视频必须设置
    video.src = videoSrc;
    video.currentTime = 1; // 跳到第1秒,避免第0帧是黑屏
 
    video.addEventListener('seeked', () => {
      const canvas = document.createElement('canvas');
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
      canvas.getContext('2d').drawImage(video, 0, 0);
      resolve(canvas.toDataURL('image/jpeg', 0.8));
    });
  });
}

注意:如果视频是跨域的,不设置 crossOrigin="anonymous" 就调用 drawImage,Canvas 会被污染,后续 toDataURL() 会抛出安全错误。


width / height — 不是你想的那个尺寸

这两个属性控制的是 HTML 元素的占位尺寸,单位是 CSS 像素,与视频本身的编码分辨率无关。

<!-- 元素宽800px,但视频编码可能是1920x1080 -->
<video src="video.mp4" width="800" height="450" controls></video>

如果不设置,&lt;video&gt; 元素会使用视频的原始分辨率作为默认尺寸,可能导致布局溢出。

最佳实践:用 CSS 控制尺寸,而不是 HTML 属性:

.video-container {
  width: 100%;
  max-width: 1200px;
}
 
.video-container video {
  width: 100%;
  height: auto; /* 保持宽高比 */
  display: block; /* 消除底部 inline 间距 */
}

视频的实际编码分辨率要通过 JS 的 video.videoWidthvideo.videoHeight 来读取,这在做 Canvas 截图时非常重要。


第四部分:播放行为属性(重点!)

这一部分是最容易踩坑的区域,也是 AI 视频项目最常遇到问题的地方。

controlscontrolslist — 原生控制栏的取舍

controls 属性开启浏览器原生控制栏。但原生控制栏在不同浏览器、不同系统上的 UI 差异很大,AI 视频产品几乎都会选择完全自定义 UI。

controlslist 是一个较新的属性,可以在保留原生控制栏的同时,禁用某些功能:

<video
  controls
  controlslist="nodownload nofullscreen noremoteplayback"
  src="video.mp4"
></video>
作用
nodownload隐藏下载按钮
nofullscreen禁用全屏按钮
noremoteplayback禁用投屏/远程播放

如果要完全自定义播放器 UI,直接去掉 controls 属性,用 JS + CSS 实现自己的控制层。


autoplay — 最被误解的属性

这是开发者投诉最多的属性。很多人发现加了 autoplay 视频不自动播放,以为是 Bug,其实是浏览器的主动拦截策略。

现代浏览器的自动播放策略(Chrome 66+):

  1. 静音视频(muted — 始终允许自动播放
  2. 有声视频 — 仅在以下情况允许:
    • 用户曾经与该页面有过交互(点击、滚动等)
    • 浏览器根据历史记录判断用户愿意在此站点自动播放(MEI 指数足够高)
  3. 任何情况下 — 不允许无声音、无交互的有声视频自动播放

正确的自动播放实现方式:

const video = document.querySelector('video');
 
// 先静音自动播放,等用户交互后解除静音
video.muted = true;
const playPromise = video.play();
 
if (playPromise !== undefined) {
  playPromise
    .then(() => {
      // 自动播放成功,可以展示"点击开启声音"的提示
      console.log('自动播放成功');
    })
    .catch(error => {
      // 自动播放被阻止,展示播放按钮让用户手动触发
      console.log('自动播放被阻止,需要用户交互', error);
      showPlayButton();
    });
}

重要video.play() 返回一个 Promise,必须处理 rejection,否则控制台会出现 Uncaught (in promise) DOMException 报错,在某些环境下还会影响后续逻辑。


muted — 不只是静音,更是自动播放的钥匙

muted 属性有两层作用:一是静音播放,二是解锁 autoplay

<!-- 这是目前唯一稳定的自动播放写法 -->
<video src="video.mp4" autoplay muted loop playsinline></video>

这个组合是 AI 视频产品中最常见的"背景视频"写法,在所有现代浏览器和移动端均可稳定自动播放。


loop — 循环播放

loop 属性让视频播放结束后自动从头开始。看起来简单,但在移动端有一个细节:部分 Android 设备在 loop 触发重播时会有短暂黑屏(因为重新触发了 seekingseeked 事件)。如果对循环的流畅度要求很高,可以监听 ended 事件手动 currentTime = 0play(),配合 preload="auto" 减少重播延迟。


preload — 性能优化的关键杠杆

preload 控制浏览器在页面加载时预先加载多少视频数据,是流量消耗和首帧速度之间的权衡。

行为适用场景
none不预加载任何数据视频列表页,节省流量
metadata只加载元数据(时长、分辨率、第一帧)推荐默认值
auto浏览器自主决定预加载量单视频详情页,追求首帧速度
<!-- 列表页:不预加载,用 IntersectionObserver 控制 -->
<video preload="none" data-src="video.mp4" poster="cover.jpg"></video>
 
<!-- 详情页:预加载元数据,平衡速度与流量 -->
<video preload="metadata" src="video.mp4" controls></video>

移动端注意preload="auto" 在移动端可能被浏览器忽略(出于省流量考虑),不要依赖它保证预加载效果。


playsinline — 移动端内联播放的通行证

这是移动端开发中最重要的属性之一。iOS Safari 和部分 Android 浏览器默认会在用户点击播放时劫持视频并强制全屏playsinline 告诉浏览器:"请在页面内播放,不要全屏"。

<video
  src="video.mp4"
  controls
  playsinline
  webkit-playsinline
  x5-playsinline="true"
  x5-video-player-type="h5-page"
></video>

各属性的覆盖范围:

属性覆盖范围
playsinlineW3C 标准,iOS 10+、现代 Android Chrome
webkit-playsinlineiOS 9 及更早版本兼容
x5-playsinline微信/QQ X5 内核,局域播放(视频在文档内播放)
x5-video-player-type="h5-page"X5 内核同层播放(允许 HTML 元素覆盖在视频上)

X5 内核的同层播放非常重要:不设置时,X5 的视频播放器是一个独立的 Native View,会遮盖视频区域上方的所有 HTML 元素(弹幕、按钮、蒙层全部失效)。设置 x5-video-player-type="h5-page" 后,视频才会真正融入 DOM 层级。1927344728.github.io


第五部分:跨域属性 crossorigin

crossorigin 是一个容易被忽视但在 AI 视频场景中非常关键的属性。

<video
  src="https://cdn.example.com/video.mp4"
  crossorigin="anonymous"
  controls
></video>
行为
不设置不发送 CORS 请求,无法在 Canvas 中使用该视频
anonymous发送不含凭证(Cookie)的 CORS 请求
use-credentials发送含凭证的 CORS 请求(需要服务器配合)

什么时候必须设置 crossorigin

只要你打算对视频做以下任何操作,就必须设置:

  • 用 Canvas drawImage(video) 截帧或处理像素
  • 用 WebGL 将视频作为纹理
  • 读取 &lt;track&gt; 字幕的文本内容(textTracks API)

不设置的后果是 Canvas 被"污染"(tainted),调用 toDataURL()getImageData() 时抛出 SecurityError

同时,服务端也必须配置 CORS

# Nginx 配置示例
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods GET;

第六部分:生产环境完整模板

综合以上所有知识点,一个覆盖 PC + 移动端 + 跨域场景的完整 &lt;video&gt; 写法:

<video
  class="video-player"
  poster="https://cdn.example.com/cover.jpg"
  preload="metadata"
  crossorigin="anonymous"
  playsinline
  webkit-playsinline
  x5-playsinline="true"
  x5-video-player-type="h5-page"
  controlslist="nodownload"
>
  <source
    src="https://cdn.example.com/video.webm"
    type="video/webm; codecs='vp9, opus'"
  />
  <source
    src="https://cdn.example.com/video.mp4"
    type="video/mp4; codecs='avc1.42E01E, mp4a.40.2'"
  />
  <track
    default
    kind="subtitles"
    src="https://cdn.example.com/subtitles_zh.vtt"
    srclang="zh"
    label="中文"
  />
  <p>您的浏览器不支持 HTML5 视频</p>
</video>

配套 CSS:

.video-player {
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  display: block;
  object-fit: contain;
  background-color: #000;
}

第七部分:属性速查表

属性类型默认值核心作用
srcURL直接指定视频源
controlsBooleanfalse显示原生控制栏
controlslistString精细控制原生控制栏功能
autoplayBooleanfalse自动播放(需配合 muted)
mutedBooleanfalse静音,解锁 autoplay
loopBooleanfalse循环播放
posterURL封面图
preloadEnummetadata预加载策略
playsinlineBooleanfalse移动端内联播放
crossoriginEnumCORS 跨域模式
widthNumber元素宽度(CSS 像素)
heightNumber元素高度(CSS 像素)
disablepictureinpictureBooleanfalse禁用画中画

小结

&lt;video&gt; 标签的每一个属性背后都有值得深挖的机制。从 &lt;source&gt; 的格式兜底策略,到 autoplay 的浏览器限制,再到 crossorigin 对 Canvas 操作的影响,以及移动端 playsinline 的各种方言写法——这些细节决定了你的视频播放器在真实用户环境中能不能稳定运行。

下一篇将深入 HTMLMediaElement API,讲清楚 readyState 状态机、buffered 时间范围、play() 的 Promise 处理,以及那条完整的视频生命周期事件链路。