跳到主要内容

图标系统与sprite

图标的文档是 symbol 注册表 + 引用,不是 200 个 HTTP 文件,也不是一张 PNG 雪碧再切 background-position。世界单位就是 icon 自己的 viewBox(常见 0 0 24 24)。页面布局只决定 CSS 盒子;相机对单图标通常是 identity——不要给每个 icon 再套一层产品相机。

1. 场景怎么切

sprite 文档:<svg> 里一堆 <symbol id viewBox>
实例:按钮里 <svg viewBox="0 0 24 24"><use href="#id"></svg>
可选:外链 sprite.svg#id(要接受不能改内部 CSS 的代价)
光栅降级:构建时导出 PNG/WebP,运行时不走 SVG

必须是节点:需要 currentColor、随主题换填色、可加 title 的实例。每个实例一棵极小的 inline SVG(或 CSS mask),不要把整张 sprite 树 inline 进每个按钮。

可以光栅:favicon、邮件、只能吃位图的原生 tab bar、以及确定永不换色的装饰。光栅要按目标 DPR 出多套,不要拉 24px SVG 再让浏览器当糊图拉伸到 96。

const SVG_NS = "http://www.w3.org/2000/svg";

export function mountIcon(
host: Element,
spriteUrl: string | undefined,
symbolId: string,
label: string,
): SVGSVGElement {
const svg = document.createElementNS(SVG_NS, "svg");
svg.setAttribute("viewBox", "0 0 24 24");
svg.setAttribute("role", "img");
const title = document.createElementNS(SVG_NS, "title");
title.textContent = label;
const use = document.createElementNS(SVG_NS, "use");
const href = spriteUrl ? `${spriteUrl}#${symbolId}` : `#${symbolId}`;
use.setAttribute("href", href);
svg.append(title, use);
host.append(svg);
return svg;
}

同源内联 sprite(页面里藏一份 display:none 的 symbol 表)对 currentColor 最稳:use 的实例吃得到外部 CSS。外链 use 在多数引擎里是独立文档,改不了内部 fill,只能整图标当图。需要换色就不要外链。机制见 符号复用与 use

装饰性图标:aria-hidden="true",不要 title。功能性图标才挂可访问名。同一页面里 symbol id 必须带前缀(icon-chevron),和插画 / 图表的 id 空间隔离。

嵌入方式选 inline 小 SVG,不要为 24px 图标走 &lt;object>。盒子尺寸走 CSS,viewBox 锁住用户空间,见 嵌入方式

适用:设计系统里 12–48px、要跟文字对齐变色的 glyph。插画型 200+ path 的「图标」按插画文档走,不要塞进 sprite。

2. 不适用

  • 把插画型复杂 SVG(滤镜、蒙版、几百 path)当 16px 图标反复 use:滤镜按实例光栅,列表一滚动主线程就没了。
  • <img src="icon.svg"> 又指望 CSS 改色:图模式没有那棵可样式的树。
  • 一个图标一个文件还走 HTTP/1:这是 sprite 要消灭的问题。打包进一份 sprite 或 URL 哈希的单文件。

构建期把 SVG 优化掉编辑器垃圾(id 冲突、内联 style)。运行时不要再跑一遍 SVGO。fill="currentColor" 写在 symbol 内 path 上,实例才能吃按钮颜色;写死 #333 的稿,主题系统救不了。

CSS mask 把 SVG 当 alpha:换色方便,但没有节点树,不能挂 title。装饰可以,导航按钮不行。

3. 失败形态

症状原因
200 个 icon 200 个请求没有 sprite / 没有打包
换主题颜色不动外链 use,或 path 写死了 fill="#000"
读屏念出一堆 path装饰 icon 没 aria-hidden,或功能性 icon 没 title/role
相邻图标串色 / 裁错symbol 的 id 全局碰撞,或 viewBox 和内容包围盒不一致
Retina 发糊实际走了 PNG 降级且只出了 1x
按钮里图标被压扁实例缺 viewBox,或 CSS 只设了宽没设高且没有 preserveAspectRatio
use 空白外链 sprite CORS / 文件未同源,或 href 写成了 .svg 无 fragment

权威资料

核对日期:2026-08-26