逐帧雪碧图:三种切帧技法
把一连串「帧」横排或竖排拼进一张 sprite sheet(雪碧图),再用 steps() 一帧帧切,就得到像翻页书一样的逐帧动画——GIF 太大、视频太重、SVG 画不出的手绘帧动画,这是最轻的做法。本页先讲清「为什么必须是
steps()」,再把同一张奔跑皇后图的三种切帧技法同台对照,最后深入较新的 object-view-box:它不只是切帧的第三种写法,本身是替换元素的「取景框」能力。
1 · 为什么用 steps(),不用 ease
逐帧动画要的是整帧跳变,而不是帧与帧之间被插值出的混合图像。 把一个周期切成 n 段台阶,在每段内保持不动、到边界才瞬跳——正好对应「一帧停留一段时间再换下一帧」。关键是台阶在哪一端跳:
| 写法 | 台阶行为 |
|---|---|
steps(n, jump-none) |
两端都不跳:n 段台阶停在 n 个值上,首帧与末帧都完整显示。逐帧动画几乎都用它——n 帧雪碧图配 steps(n, jump-none),每帧都看得到。 |
steps(n, jump-end) / step-end |
默认值。在每段结束时跳,等于跳过最后一帧的停留——若用它切 n 帧雪碧图,会少看到一帧或末帧一闪而过。 |
steps(n, jump-start) / step-start |
在每段开始时立刻跳,跳过第一帧的停留。 |
steps(n, jump-both) |
两端都跳,实际在 n+1 个位置里去头去尾。 |
下面这个三色方块共 3 帧,拖动下拉框换 steps() 的跳变端,看首末帧是否完整:
记忆要点:帧数 = steps 的 n,而 background-position(或 translate)的终点要走满整张图。用 jump-none 时,n 段台阶恰好停在 0 → 1/n → … → n/n 这 n 个「帧位」上,首尾都不丢。
2 · 同一张图,三种切帧技法
下面同一张 7 帧竖排的奔跑皇后图,分别用 background-position、overflow: hidden + translate、object-view-box 裁剪三种方式跑出同样的逐帧动画。三者帧数都是 7,都用 steps(7, jump-none) 推进。
| 技法 | 原理 | 优点 / 代价 |
|---|---|---|
| background-position | 图当背景,background-size 放大成 N 屏,动 background-position 从 0 滑到 100% 逐屏露出。 |
最经典、兼容性最好,任何元素都能当画布;但图是背景,不参与文档流尺寸,得手算 background-size 百分比。 |
| overflow + translate | 真实 <img> 放进固定大小的窗口(overflow: hidden),动 translate 把长条图整体上推一帧高。 |
用的是真 <img>(可被语义 / 懒加载 / srcset 管理);代价是多一层窗口元素裹着。 |
| object-view-box | 直接给 <img> 一个「取景框」,动画里逐帧移动取景框,无需外层窗口、无需背景 trick。 |
最干净、语义最好;代价是较新(Chromium 104+),Safari / Firefox 尚未支持,要降级。 |
第三种里有个小技巧:object-view-box 的值不能直接被 @keyframes 插值,所以先用 @property --nth-child { syntax: "<integer>" } 注册一个整数自定义属性,动画里把它从 0 跳到 6(配 steps(7)),再用
calc() 把这个整数换算成 inset() 的偏移。
3 · 深入 object-view-box:替换元素的「取景框」
切帧只是它的一个用法。object-view-box 的本质,是给替换元素(<img> / <video> / <canvas>)定义一个矩形「视图框」,只把框内那块当作元素要显示的内容——就像 SVG 的
viewBox,只不过作用在任意位图上。框选出来的区域,再按 object-fit / object-position 摆进元素的盒子里(框比盒子小 = 放大聚焦)。一句话:不动原图、不切图、不加容器,纯 CSS 就能聚焦到图片的任意一块。
拖动下面三个滑块移动、缩放取景框,左边源图上的框与右边的裁切结果实时联动:
4 · 语法:inset() / rect() / xywh()
| 写法 | 含义 |
|---|---|
object-view-box: inset(top right bottom left) |
从图片四边各往内缩多少,缩剩下的矩形就是取景框。最常用,和 clip-path: inset() 同形;上面 demo 用的就是它。 |
object-view-box: xywh(x y w h) |
直接给取景框的左上角坐标 + 宽高。想「从 (x, y) 取一块 w×h」时比 inset 更直观。 |
object-view-box: rect(top right bottom left) |
给取景框四条边的绝对位置(相对图片左上角),语义同 clip-path: rect()。 |
配合 object-fit / object-position |
取景框定的是「取哪块」,object-fit(fill / contain / cover…)定的是「这块怎么摆进元素盒子」。取景框比盒子比例不同就会被拉伸或留边——和普通 <img> 的 object-fit 行为完全一致。 |
5 · 不只是切帧——还能干什么
- 聚焦 / 二次构图:同一张产品图,在卡片里只露出主体那一块,无需后端再裁一版缩略图。
- 去掉图片自带的留白:设计稿导出的图常带透明边距,用
inset往里收一圈即可,不必重新导图。 - 响应式裁切(art direction):配 media query / container query 在不同断点取不同区域,省掉多套
<picture>源。 - 雪碧图切帧:就是本页的主角用法,横排 / 竖排都行。
浏览器支持:object-view-box 自 Chromium 104(2022)起支持,Safari 与 Firefox 仍未实现(核对于 2026-07)。生产环境务必降级——上面三技法对照里,background-position 与 overflow + translate 就是它的降级方案;或用
@supports (object-view-box: inset(0)) { … } 做特性查询。详见 MDN · object-view-box。