提示

返回博客列表

鼠标悬停进度条就能预览画面?聊聊视频缩略图与雪碧图技术

鼠标悬停进度条就能预览画面?聊聊视频缩略图与雪碧图技术

在 B 站或 YouTube 看视频时,鼠标悬停在进度条上,会弹出一个小预览图,让你快速定位想看的片段。这个功能背后是一张雪碧图(Sprite)——把几十上百帧画面拼成一张大图,前端按时间映射到对应的位置裁切显示。本文拆开视频缩略图生成的完整技术链路。

TL;DR:视频缩略图预览 = ffmpeg 定时截图 + ImageMagick 拼雪碧图 + 前端 CSS background-position 裁切。也可以用 VTT 缩略图轨道的标准方案。本质是在"下载整个视频"和"完全不知道内容"之间,给用户一个低成本预览的折中方案。

目录

一、为什么要用雪碧图

直观想法是"生成 100 张缩略图,前端加载 100 张小图"。但 HTTP 请求很贵——100 张图 = 100 次请求,页面会卡成 PPT。

雪碧图的思路:把 100 张小图拼成 1 张大图,1 次请求全部搞定。前端用 CSS 的 background-position 只显示对应格子。

雪碧图结构(5列 × 20行 = 100帧):
┌───┬───┬───┬───┬───┐
│ 0 │ 1 │ 2 │ 3 │ 4 │  ← 第 0 秒 到 第 4 秒
├───┼───┼───┼───┼───┤
│ 5 │ 6 │ 7 │ 8 │ 9 │  ← 第 5 秒 到 第 9 秒
├───┼───┼───┼───┼───┤
│... │...│...│...│...│
└───┴───┴───┴───┴───┘

一张大图解决所有预览,这就是雪碧图的价值。

二、用 ffmpeg 定时截图

第一步:从视频中按固定间隔抽帧。

# 每 10 秒截一张,缩放到 160x90
ffmpeg -i video.mp4 \
  -vf "fps=1/10,scale=160:90" \
  thumb_%04d.png

参数解释:
- fps=1/10:每 10 秒 1 帧(即 0.1 fps)
- scale=160:90:缩略图分辨率(16:9 比例)

如果你的视频是 30 分钟(1800 秒),fps=1/10 会生成 180 张缩略图。如果想更密集(比如每 5 秒一张),改成 fps=1/5

# 更精确的方式:指定总帧数
# 生成 100 张均匀分布的缩略图
ffmpeg -i video.mp4 -vf "fps=1/$(echo '1800/100' | bc)" thumb_%04d.png

三、拼成雪碧图

第二步:把散图拼成一张大图。ImageMagick 的 montage 命令是最简单的方案:

# 把当前目录所有 thumb_*.png 拼成雪碧图
# 每行 10 张,自动换行
montage thumb_*.png -tile 10x -geometry 160x90+0+0 sprite.jpg

参数解释:
- -tile 10x:每行 10 张
- -geometry 160x90+0+0:每个格子 160×90,无间距
- 输出 sprite.jpg(用 JPG 格式体积更小)

如果你需要更精细的控制,可以用 Python + Pillow:

from PIL import Image
import glob

thumb_files = sorted(glob.glob("thumb_*.png"))
cols = 10
rows = (len(thumb_files) + cols - 1) // cols

sprite = Image.new('RGB', (160 * cols, 90 * rows))
for i, f in enumerate(thumb_files):
    img = Image.open(f)
    x = (i % cols) * 160
    y = (i // cols) * 90
    sprite.paste(img, (x, y))
sprite.save('sprite.jpg', quality=85)

四、前端如何"裁切"显示

拿到雪碧图后,前端根据鼠标在进度条上的位置,算出对应的格子坐标:

<div id="thumbnail" style="
  width: 160px; height: 90px;
  background-image: url('sprite.jpg');
  background-position: -320px -180px;  /* 第3列第2行 */
  background-size: 1600px 1800px;      /* 总尺寸 = 160*10 × 90*20 */
"></div>

JavaScript 动态计算 background-position

const COLS = 10;
const THUMB_W = 160, THUMB_H = 90;

progressBar.addEventListener('mousemove', (e) => {
  const percent = e.offsetX / progressBar.offsetWidth;
  const frameIndex = Math.floor(percent * totalFrames);

  const col = frameIndex % COLS;
  const row = Math.floor(frameIndex / COLS);

  thumbnail.style.backgroundPosition = `-${col * THUMB_W}px -${row * THUMB_H}px`;
  thumbnail.style.display = 'block';
});

五、VTT 缩略图标准方案

除了自造雪碧图,还有一种更标准的做法——WebVTT 缩略图轨道

WEBVTT

00:00:00.000 --> 00:00:10.000
sprite.jpg#xywh=0,0,160,90

00:00:10.000 --> 00:00:20.000
sprite.jpg#xywh=160,0,160,90

00:00:20.000 --> 00:00:30.000
sprite.jpg#xywh=320,0,160,90

每行指定时间段 + 雪碧图上的裁切坐标(xywh = x, y, width, height)。前端解析 VTT 文件后,直接取对应坐标显示。这种方式的好处是:
- 标准化,播放器可直接支持
- 可以配合多张雪碧图(超长视频分多张)
- 语义清晰,维护方便

六、性能优化建议

优化项 做法
缩略图分辨率 160×90 就够了,别用 320×180
图片格式 JPG quality=85,体积比 PNG 小 5-10 倍
雪碧图分片 长视频拆成多张雪碧图,每张不超过 2MB
懒加载 鼠标悬停进度条时才加载雪碧图
CDN 缓存 雪碧图不常变,设长缓存时间
# ffmpeg 直接输出 JPG(省去 PNG→JPG 转换)
ffmpeg -i video.mp4 -vf "fps=1/10,scale=160:90" -q:v 3 thumb_%04d.jpg

七、合规与温馨提示

技术是中性的,用法见人心。我们强烈建议:

  • 仅对你自己拥有版权平台明确允许的视频生成缩略图;
  • 不将缩略图用于误导性用途(如封面党);
  • 尊重创作者与平台服务条款。

视频缩略图雪碧图是一个"小功能、大体验"的典型。它让用户在不下载整个视频的情况下快速预览内容——本质是在带宽成本和用户体验之间找一个优雅的平衡点。如果你在自己的视频站上实现了这个功能,用户拖进度条的体验会直线上升。

本文由 VidDown 技术博客原创发布。VidDown 是一个免费、本地优先的在线视频解析与开发者工具站,支持多平台视频下载、格式转换、m3u8 合并等实用功能,所有数据处理均在本地完成,保护你的隐私。欢迎访问 www.viddown.cn 体验。

想亲手试试?用 VidDown 一键解析下载

粘贴视频链接即可解析,多平台支持、网页端即用;下载桌面客户端解锁海外平台本地解析,开通会员更享不限次下载。

顶部