12k
All articles

如何用 MP4 视频替换动画 GIF

将动画 GIF 替换为 MP4 或 WebM 视频,使用 autoplay muted loop playsinline 标记,并提供 ffmpeg 转换和 LCP 提示。

OpenReplay Team
OpenReplay Team
如何用 MP4 视频替换动画 GIF

要用视频替换动画 GIF,先将片段转换为 H.264 MP4 和 VP9 WebM,然后使用 <video autoplay muted loop playsinline> 同时嵌入两者,并将 WebM 的 <source> 放在最前面。

如果 Lighthouse 对你的产品页面或文档中的动画内容发出了警告,原因通常是某个体积达数兆字节的屏幕录制 GIF。本文将讲解为什么 GIF 在体积上处于劣势、如何进行转换(浏览器工具或 ffmpeg,任你选择)、确切的标记写法、这次替换对 LCP 的影响,以及在哪些场景下 GIF 仍然是正确的选择。

核心要点

  • 动画 GIF 将每一帧存储为一张近乎完整的图像,且每帧最多只有 256 种颜色,因此完全无法享受视频编解码器所依赖的帧间压缩。
  • muted 是让浏览器允许自动播放的关键,而 playsinline 则能阻止 iOS 强制视频进入全屏。
  • 浏览器播放的是它能解码的第一个 <source>,而不是体积最小的那个,因此 WebM 源必须放在 MP4 回退源之前。
  • 从 Chrome 116 起,没有 poster<video> 也可以成为 LCP 元素,记录的时间点是其首帧到达屏幕的那一刻。
  • 体积节省完全取决于具体片段;请自行测量替换前后的数据,而不要轻信引用的百分比。

为什么 GIF 是承载动态内容的错误容器?

动画 GIF 将每一帧存储为一张近乎完整的图像,每帧调色板最多 256 种颜色,因此完全无法享受视频格式所围绕设计的帧间压缩;而且它只能通过软件解码,而 H.264 和 VP9 在大多数设备上都有硬件解码通路。GIF89a 规范明确指出,该格式并非为承载动画而设计,仅以有限的方式允许动画;循环播放是后来由浏览器添加的。这就是你需要了解的全部历史。

实际后果是:几秒钟的屏幕录制,作为 GIF 通常会达到数兆字节,而作为视频则只有几百千字节。Lighthouse 会提示这一点:从 Lighthouse 13 开始,GIF 转视频的建议已归入「Improve image delivery」洞察中。

一个真实的转换案例及真实数据

Google 的 web.dev 相关指南公布了一个诚实的示例,使用与下文相同的命令进行转换:一个 3.7 MB 的源 GIF 变成了 551 KB 的 MP4 和 341 KB 的 WebM。节省幅度完全取决于片段的内容、帧率和尺寸,因此在某个文件上测出的百分比无法推广到你的文件上。

文件体积
源 GIF3.7 MB
MP4(H.264,CRF 25)551 KB
WebM(VP9,CRF 41)341 KB

把这些数字当作一个数据点,而不是一条规则。高动态素材的压缩表现与基本静态的终端录制截然不同。先用下面的步骤跑一遍你自己的片段,比较字节数之后再做决定。

如何用视频替换 GIF:制作文件

你需要两个文件:一个用于通用播放的 MP4,以及一个通常更小的 WebM。每个步骤都提供浏览器工具和等效的 ffmpeg 命令。

第 1 步:GIF 转 MP4。 使用完全在浏览器本地运行的 GIF to MP4 converter,或使用 ffmpeg:

ffmpeg -i input.gif -vf "crop=trunc(iw/2)*2:trunc(ih/2)*2" \
  -vcodec libx264 -pix_fmt yuv420p -b:v 0 -crf 25 -f mp4 output.mp4

-pix_fmt yuv420p 能保证文件在任何地方都可播放;CRF 取值范围为 0 到 51,数值越低质量越高,而 -b:v 0 会在 CRF 模式下关闭码率限制。crop 滤镜是 web.dev 的变通方案,用于解决 libx264 拒绝奇数像素尺寸的问题。

第 2 步:制作 WebM 文件,用作第二个 <source>。在浏览器中,把刚生成的 MP4 输入 MP4 to WebM converter。若使用 ffmpeg,则直接从原始 GIF 编码,以避免片段被二次压缩:

ffmpeg -i input.gif -c:v libvpx-vp9 -b:v 0 -crf 41 output.webm

VP9 的 CRF 标度与 x264 不同,这就是为什么此处的 41 是一个合理的默认值,而不是低质量设置。

第 3 步:可选的调优。 如果结果仍然偏大,可用视频压缩器从分辨率和质量两方面缩减,或使用 ffmpeg:

ffmpeg -i output.mp4 -vf scale=640:-2 -crf 28 -movflags faststart smaller.mp4

-movflags faststart 会把 MP4 的元数据移到文件开头,这样在下载完成之前就能开始播放。

表现得像 GIF 的标记

要让视频表现得像 GIF,请使用 <video autoplay muted loop playsinline>muted 是让浏览器允许自动播放的关键,而 playsinline 则能阻止 iOS 强制视频进入全屏。

<video autoplay muted loop playsinline width="640" height="360">
  <source src="clip.webm" type="video/webm">
  <source src="clip.mp4" type="video/mp4">
</video>

Chrome 的自动播放策略允许静音视频自行开始播放,并会在访客与站点产生交互之前阻止带声音的自动播放。WebKit 针对 iOS 的视频策略规定,只有在视频静音或不含音轨时,才允许其无需手势即自动播放;而 iPhone 需要 playsinline 才能就地播放片段。源的顺序很重要,因为浏览器并不会挑选最优的 <source>,而是播放它能解码的第一个,所以体积更小的 WebM 要放在前面。这个顺序在任何环境下都是安全的:caniuse 的 WebM 支持表显示 macOS 上的 Safari 16 已完全支持,iOS 上的 Safari 从 17.4 起也已支持,因此 2018 年前后关于 Apple 不支持 WebM 的警告已不再适用。widthheight 属性用于预留布局空间,这与你为图像提供的 CLS 友好处理是一致的。

当这些属性中缺少某一个时,故障是无声的:没有控制台错误,没有图像损坏图标,只有一个静止的首帧。会话回放能呈现每个用户实际看到的页面,这是发现生产环境中从未启动的自动播放视频的唯一可靠方式。

这次替换对 Largest Contentful Paint 有何影响?

<img> 换成 <video> 会改变哪些元素可以成为你的 LCP 候选。较早的指南说没有 poster<video> 对 LCP 是不可见的,但这在 Chrome 116 中发生了变化:Chromium 指标变更日志记录了 video 元素现在与图像同等参与 LCP 评定,其时间戳取自首帧到达屏幕的那一刻。所以不要仅为了操纵指标而添加 poster;自动播放的视频会立即绘制首帧,poster 图像根本不会被看到。如果你的首屏动画是最大的元素,那么它的 LCP 时间现在取决于首帧到达的速度——这又是一个保持文件小巧的理由。

什么时候 GIF 仍然更胜一筹?

在你无法控制标记的地方,GIF 仍然更胜一筹:聊天消息、邮件客户端和 GitHub README 会内联渲染 GIF,但不会嵌入自动播放的视频。在这些环境中,可移植性胜过页面体积,正确的做法是对 GIF 进行有损压缩,而不是转换格式。gifsicle--lossy 选项(前身是独立的 giflossy 项目)以画质瑕疵换取体积:

gifsicle -O3 --lossy=80 -o smaller.gif input.gif

有损值越高,允许的瑕疵越多、文件越小;不断调整直到输出看起来不可接受,然后退回一档。

总结

在任何由你控制标记的页面上,动态内容都应该放在带有两个源的 <video> 元素中,而不是 GIF 里。挑出站点上体积最大的那个 GIF,按上面的步骤转换,然后自己比较字节数;接着上线这套四属性标记(WebM 放在前面),并查看一段真实会话的回放,确认它确实在播放。

常见问题

我可以只提供 MP4 而跳过 WebM 文件吗?

可以。采用 yuv420p 像素格式的 H.264 MP4 能在所有现代浏览器中播放,因此单源的 video 元素在任何地方都能工作,不会出问题。WebM 版本是体积优化,而非兼容性要求:VP9 通常能在相近质量下生成更小的文件。先上线 MP4,如果页面体积仍让你困扰,之后再在它上方添加 WebM 源。

为什么即使设置了 autoplay、muted、loop 和 playsinline,我的视频仍然不自动播放?

这是用户代理策略在阻止播放,而不是你的标记有问题。iOS Safari 在设备处于低电量模式时会暂停自动播放,并改为叠加一个播放按钮;其他浏览器的省电或省流量模式也可能有类似行为。可以用 JavaScript 检测:video.play() 返回一个 promise,因此请处理其 rejection,显示控件或静态回退图像,而不是留下一个静止的画面。

loading='lazy' 在 video 元素上有效吗?

在基于 Chromium 的浏览器中有效。Chrome、Edge 和 Opera 会把懒加载视频的下载、poster 获取和自动播放延迟到该元素接近视口时,MDN 也记录了这个属性。HTML 规范中相应的补充仍在进行中,Firefox 和 WebKit 都对该特性给出了积极的标准立场,但都尚未发布实现。不支持的浏览器会直接忽略该属性并立即加载,因此今天为屏幕外的 GIF 替代视频添加它是安全的。

动画 WebP 是介于 GIF 和视频之间的良好折中方案吗?

有时是。动画 WebP 可以在普通的 img 元素中工作,支持 24 位色而非 GIF 的 256 色调色板,通常也比等效的 GIF 文件更小。但它无法与真正的视频编解码器相比:H.264 或 VP9 在压缩屏幕录制时的表现要好得多。在环境要求使用 image 标签但接受现代格式的地方使用动画 WebP,而在你能控制标记的地方一律使用视频。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.