学完这篇,你能把网页里的视频从「一进页面就偷偷下载几十兆」改成「按需加载、首屏有封面、网速差自动降清晰度」——三个关键点分别是 preload、poster 和自适应码率(ABR)。
第一步:先决定 preload 用哪个值
preload 决定浏览器在你按下播放键之前,愿意先下载多少视频数据。三个常用值:
<!-- 列表页、一屏多个视频:一个字节都别提前下 -->
<video src="a.mp4" preload="none" poster="a-cover.jpg" controls></video>
<!-- 详情页(最常见):只拉文件头,拿到时长和首帧 -->
<video src="a.mp4" preload="metadata" poster="a-cover.jpg" controls></video>
<!-- 只有一个视频、且确定用户会看:可以激进一点 -->
<video src="a.mp4" preload="auto" poster="a-cover.jpg" controls></video>
选值的原则就一句话:页面上视频越多、位置越靠下,越要往 none 靠。一屏 10 个视频全写 auto,等于让用户白下载几百兆。
注意:preload 只是给浏览器的建议,不是强制命令,浏览器会结合网络状态和自己的策略决定听不听。所以别把「省流量」完全寄托在它身上,真正稳的做法是配合下面的懒加载。
第二步:poster 让首屏不黑屏
poster 就是视频还没播放时显示的那张图。没有它,视频区域通常是一块黑的。
<video
src="a.mp4"
poster="a-cover.jpg"
preload="none"
playsinline
controls
style="width:100%; aspect-ratio:16/9; object-fit:cover;">
</video>
几个实际要点:
- 尺寸和视频比例一致(一般 16:9),并压到 30–80KB 的 WebP/JPG,否则封面图本身又变成新的性能问题。
- 用
aspect-ratio 或 width/height 占位,否则视频加载出来时页面会跳一下(CLS),体验很差。
preload="none" + poster 是性价比最高的组合:既不给首屏加负担,用户也看不到黑块。
注意:只写 preload="none" 不写 poster,用户看到的是一块空黑区域,很容易以为页面坏了。另外别把大段说明文字塞进海报图里,手机上会被裁掉。
第三步:做懒加载,把 preload 落到实处
列表页里,视频离视口还很远时不该有任何请求。用 IntersectionObserver 靠近再赋值:
<video poster="a-cover.jpg" preload="none" playsinline controls data-src="a.mp4"></video>
<script>
const io = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (!e.isIntersecting) return;
const v = e.target;
v.preload = 'metadata'; // 靠近了才允许拉元数据
v.src = v.dataset.src;
io.unobserve(v);
});
}, { rootMargin: '200px' });
document.querySelectorAll('video[data-src]').forEach(v => io.observe(v));
</script>
第四步:自适应码率(ABR)怎么做
前面三步解决的是「不该下的别下」,这一步解决的是「该下的别下太大」。ABR 的基本流程是:把同一个视频转成 360P/720P/1080P 等多档,切片后写一份主播放列表,播放器根据实时带宽自动切档。
转码(以 720P 为例,其他档同样跑一遍):
ffmpeg -i input.mp4 -c:v libx264 -b:v 1200k -s 1280x720 \
-c:a aac -b:a 128k -f hls -hls_time 6 -hls_playlist_type vod \
-hls_segment_filename "720p_%03d.ts" 720p.m3u8
手写主列表 master.m3u8:
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=1500000,RESOLUTION=1280x720
720p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=854x480
480p.m3u8
页面播放(Safari 原生支持 HLS,其他浏览器用 hls.js):
<video id="v" controls poster="cover.jpg" preload="metadata" playsinline></video>
<script src="/js/hls.min.js"></script>
<script>
const v = document.getElementById('v'), url = '/hls/master.m3u8';
if (v.canPlayType('application/vnd.apple.mpegurl')) {
v.src = url;
} else if (window.Hls?.isSupported()) {
const hls = new Hls({ capLevelToPlayerSize: true });
hls.loadSource(url);
hls.attachMedia(v);
}
</script>
如果不想上 HLS 这套基础设施,低配替代方案是准备两三个 MP4,用 JS 粗判网络再选源。它做不到播放中平滑切换,但胜在零依赖、零转码成本,小站够用。
注意:HLS 的 .m3u8 和 .ts 分片要能被跨域请求,服务器需返回正确的 CORS 头,且 Nginx/Apache 要给 .m3u8 配置 application/vnd.apple.mpegurl。另外同一个 <video> 上不要同时存在 src 属性和 <source> 子标签,浏览器会只认 src。
第五步:别忘了 Range 请求和缓存
用户拖动进度条时,浏览器发的是带 Range 头的请求。如果服务器不支持 206 分片响应(比如用 PHP 一次性把整个文件吐出来),每次拖动都会从头下载,前面所有优化全废。Nginx、Apache 默认支持,自己写流式输出时要注意。
再把 .mp4/.ts/.jpg 封面设个长缓存(Cache-Control: max-age=31536000 + 文件名带 hash),二次访问就几乎零成本。
小结
preload 按位置分档:列表用 none,详情页用 metadata,auto 慎用。
poster 必配,比例对齐、控制在 80KB 内,并用 aspect-ratio 占位防抖动。
- 列表用 IntersectionObserver 懒加载,把「不该下」的请求彻底掐掉。
- 码率自适应优先 HLS + hls.js,多档转码 + master.m3u8,播放器自动切档。
- 服务器必须支持 Range 请求,静态资源配长缓存。