🧩 第 6 课 · CSS 精灵图

第六课:一张大图,装下所有小图标

这是第 6 课的作业。课上讲的「雪碧图(sprite,也叫精灵图)」当时听起来很玄, 其实就是把很多小图标拼成一张大图,再靠 background-position 把背景挪来挪去, 让每个元素只露出自己要的那一格。

原作业只有三个空白方块、连一个字说明都没有,看上去完全不知道在演示什么。 这一版把坐标抽成了变量、补上了推导过程,并做成「常态 / 悬停」并排对照。

一、这张图长什么样

素材是一张 440 × 440 的正方形图,按 4 × 4 的网格排布, 实际放了 14 个图标(末尾有两格是空的)。 本页只用到其中 6 个(3 组图标 × 常态 / 悬停两个状态)。

CSS 雪碧图素材:4 行 4 列网格里共 14 个圆形图标,包含人物与音符、字母 A、盾牌、钱袋、赏字、数据库等图案,实心与描边两种风格交错排列
整张素材图(本地文件 common/music126.png)。 同一个图案有实心版和描边版,而且两者是交错排列的、 并不是按行分开:第 1 行第 4 格、第 2 行第 3 格、第 3 行第 1、2 格就是描边版。 这正是雪碧图最经典的用法:把「常态」和「按下 / 选中态」放在同一张图里, 切换时只要改一下 background-position,不用再请求第二张图片。
坐标是怎么算出来的?
整张图 440px 宽、4 列 → 每行每格的步进 --cell: 110px。
每格里图标的内容其实只有 90 × 90,四周各留了 10px 空白;窗口开成 --icon: 100px,坐标再让出 --gap: 5px, 图标就正好居中、四周各剩 5px 干净留白。
于是取第 c 列第 r 格的公式是:
x = -(gap + c × cell),y = -(gap + r × cell)
→ 第 0 / 1 / 2 列分别是 -5px、-115px、-225px。
⚠️ 别写成 -(c × (cell + gap)):那样第 0 格算出 0(少偏 5px)、 第 2 格算出 -230px(多偏 5px),只有第 1 格碰巧是对的。

二、坐标速查

三、常态 / 悬停对照演示

左边是常态,把鼠标移上去它会切到悬停态;右边直接把悬停后的样子并排摆出来, 方便对照——两态只差一个坐标,切换过程配了 0.2s 的过渡。

常态 · 悬停我试试 -225px -225px
悬停态 -5px -5px

角色 / 攻击:同一个「人 + 音符」图案的两套画法。常态用描边版显得轻, 悬停换成实心版立刻变重——这就是最省事的交互反馈,用户一碰就知道「这个能点」。

常态 · 悬停我试试 -5px -115px
悬停态 -115px -5px

字母 A / 文字:注意这两格的横坐标差了 110px, 正好是一格的距离——因为在图里它们就是左右相邻的两个格子。

常态 · 悬停我试试 -115px -115px
悬停态 -225px -5px

盾牌 / 防御:换成实心版后颜色变重、对比更强,适合表达「已开启防护」这类确认状态。

两个容易被忽略的细节:
1)方块那层浅蓝底色是故意留的:图标内容只有 90 × 90,而窗口是 100 × 100, 所以四周会露出一圈底——它正好把「取景窗口」的边界画了出来。原作业在这里写的是 background-color: pink,圆形图标的四个角会直接透出粉色,看起来像脏点; 底色不是不能用,是要挑一个看起来是有意为之的颜色。
2)没有 transition 的时候,坐标是「啪」一下跳过去的; 加上 transition: background-position .2s,两态之间才有了过渡。

四、小结

雪碧图的核心就一句话:元素不换图,只换背景的位置。 它的好处是省请求(16 个图标只下 1 张图),代价是坐标全靠手算、图一改就全体错位, 所以现在更常见的是 SVG 图标或字体图标。但把这套坐标推导清楚, 对理解 background-position 的百分比、负值规则特别有帮助—— 后面学 object-fit、雪碧动画(steps() 逐帧)时, 用的还是同一套思路。