我们有一张营销落地页,首屏放着三段动画 GIF —— 一段展示产品工作的主视觉循环,加两段功能 动画。合计 11 MB。Lighthouse 给手机端打了 64 分,LCP 4.2 秒,Total Blocking Time 380 ms。把这三段 GIF 换成 MP4 —— 视觉内容相同、循环行为相同 —— 一个下午就把分数推到 了 92。本文是这次操作的全程记录。
起点
页面是一个相当标准的 Next.js 营销站。Tailwind、静态生成、部署到 Vercel。三段 GIF 都是从 After Effects 用 720p、30fps、中等优化导出的。大小:
- 主视觉动画(5 秒,720p):5.8 MB
- 功能 1(3 秒,600p):2.9 MB
- 功能 2(4 秒,600p):2.4 MB
动画总传输量:11.1 MB。在 Slow 4G(Lighthouse 移动端档位)下,差不多要 27 秒才能完整加载。 页面文本很快渲染出来,但主视觉一直停在"加载中"的占位上太久 —— 这正是 LCP 要扣分的状态。
起点的 Lighthouse 报告
| 指标 | 分数 |
|---|---|
| Performance | 64 |
| Largest Contentful Paint | 4.2 秒 |
| Total Blocking Time | 380 ms |
| Speed Index | 5.8 秒 |
| Cumulative Layout Shift | 0.04 |
转换
我们用两套设置把每段 GIF 都跑了 ffmpeg:H.264 的 MP4 用作广覆盖兜底,VP9 的 WebM 给支持 的浏览器更小的传输量。命令:
ffmpeg -i hero.gif -movflags faststart -pix_fmt yuv420p -crf 22 -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2:flags=lanczos" -an hero.mp4
ffmpeg -i hero.gif -c:v libvpx-vp9 -crf 30 -b:v 0 -deadline good -cpu-used 2 -row-mt 1 -pix_fmt yuv420p -an hero.webm
转换后的体积令人印象深刻:
| 资源 | GIF | MP4 (H.264) | WebM (VP9) |
|---|---|---|---|
| 主视觉 | 5.8 MB | 320 KB (-94%) | 240 KB (-96%) |
| 功能 1 | 2.9 MB | 180 KB (-94%) | 140 KB (-95%) |
| 功能 2 | 2.4 MB | 150 KB (-94%) | 110 KB (-95%) |
总传输量从 11.1 MB 降到 650 KB(H.264),VP9 下是 490 KB。17–22 倍的削减。
标记的改动
原标记是 <img src="hero.gif" alt="...">。替换后:
<video autoplay loop muted playsinline poster="hero-poster.jpg">
<source src="hero.webm" type="video/webm">
<source src="hero.mp4" type="video/mp4">
</video>
poster 是视频开始解码前显示的一张静帧(JPEG,约 30 KB)。这是解锁 LCP 的关键 —— 浏览器会把这张 poster 当作"有内容的绘制",于是 1 秒内就达到了。
转换后的 Lighthouse 报告
| 指标 | 前 | 后 |
|---|---|---|
| Performance | 64 | 92 |
| LCP | 4.2 秒 | 1.4 秒 |
| TBT | 380 ms | 110 ms |
| Speed Index | 5.8 秒 | 1.9 秒 |
| CLS | 0.04 | 0.02 |
LCP 降了 67%,TBT 降了 71%。TBT 改善的原因:软件 GIF 解码原本在每一帧都阻塞主线程;硬件 MP4 解码会卸载到单独的进程。
注意事项与坑
- autoplay 需要 muted。iOS 和 2018 年以后的多数浏览器都会拦下含音视频的 自动播放。autoplay 的循环视频上始终设
muted。 - iOS 需要 playsinline。否则 iOS 在轻触时会把视频拉成全屏播放器。
- H.264 要求偶数尺寸。该编码器要求宽高都被 2 整除。用
scale=trunc(iw/2)*2:trunc(ih/2)*2滤镜。 - Safari 需要 yuv420p。其他像素格式 Chrome 能解,Safari 会静默失败。
- 不要把视频用于静态装饰。如果"动画"几乎不动(缓慢的 zoom、轻微的 pan), 静态图或 CSS 动画都更便宜。
Lighthouse 之外:真实用户层面的影响
Lighthouse 的数字是合成分。真正要看的是现场数据 —— 真实网络中的真实访客。改动后两周内 我们看到:
- 线上 p75 LCP:3.8 秒 → 1.6 秒(Chrome User Experience Report)
- 跳出率:移动端 -8%(GA 数据)
- CDN 流量账单:仅营销站每月 -$340
带宽节省其实比不上之前用调色板技巧和抖动死磕 GIF 浪费掉的工程时间。一旦跨过格式边界, 那些挣扎都不重要了。
怎么在自己站点上做这件事
把分数从 64 推到 92 的五步:
- 在 DevTools 的 Network 面板里找出页面上每一个
.gif。 - 把每一个都转成 MP4 (H.264) 和 WebM (VP9)。一次性可以用我们的 GIF → MP4 转换工具,两种格式都能出,全程跑在浏览器 内。要进流水线就用上面的 ffmpeg 命令。
- 为每段视频生成一张静帧 JPEG poster。
ffmpeg -i input.gif -vf "select=eq(n\\,0)" -vframes 1 poster.jpg。 - 按上面的写法把每个
<img>换成<video>。 - 重新跑 Lighthouse。确认分数跳了,LCP 降了。
总耗时:我们维护的站点里,每页大约 20 分钟。在数千用户的每次页面访问上复利累积下来,这是 我们这一年里做过的单笔回报最高的性能修复。