SVG 图标方案:不引图标库也能有成套图标
图标库动辄几百 KB,而你只需要十几个图标。用 SVG symbol,几 KB 搞定,还能随主题变色。
一、三种方案对比
| 方案 | 体积 | 可控性 | 缺点 |
|---|---|---|---|
| 图标字体(Font Awesome) | 大(整包) | 只能改颜色和大小 | 加载失败显示方框、有 FOUC |
| 图标库组件(react-icons 等) | 按需引入还行 | 好 | 引入依赖 |
| 内联 SVG symbol | 极小 | 完全可控 | 要自己收集 SVG |
本站只有几个图标(搜索、太阳/月亮、箭头),全部内联,零额外请求。
二、symbol 雪碧图
在 HTML 里放一个隐藏的 SVG,里面定义多个 <symbol>:
<!-- 放在 body 最前面,display:none -->
<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
<symbol id="i-search" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round">
<circle cx="11" cy="11" r="7"/>
<path d="M20 20l-3.5-3.5"/>
</symbol>
<symbol id="i-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round">
<circle cx="12" cy="12" r="4"/>
<path d="M12 2v2M12 20v2M2 12h2M20 12h2"/>
</symbol>
<symbol id="i-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round">
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/>
</symbol>
</svg>使用:
<svg class="icon"><use href="#i-search"></use></svg>三、currentColor:自动跟随文字颜色
这是 SVG 图标相对图标字体最大的优势:
<symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<!-- stroke="currentColor" 让描边用继承来的 color -->
</symbol>.btn { color: #333; }
.btn:hover { color: #0066ff; } /* 图标自动跟着变 */
.icon {
width: 1em;
height: 1em;
vertical-align: -0.125em; /* 与文字基线对齐 */
fill: none;
}主题切换时图标自动变色,不需要维护两套资源。本站的搜索图标和主题按钮就是这么做的。
四、尺寸控制
.icon {
width: 16px;
height: 16px;
}
/* 或跟随字号 */
.icon-inline {
width: 1em;
height: 1em;
}
/* 大图标单独设 */
.icon-lg { width: 24px; height: 24px; }关键:viewBox 必须有,SVG 才能正确缩放。
五、两种风格
| 风格 | 属性 | 适用 |
|---|---|---|
| 描边(outline) | fill="none" stroke="currentColor" | 现代、轻量(本站用这种) |
| 填充(solid) | fill="currentColor" | 实心、醒目 |
同一套 symbol 里不要混用,视觉会不统一。
# 描边风格统一参数(Lucide / Feather 的规范)
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"六、图标从哪来
| 来源 | 特点 |
|---|---|
| Lucide(lucide.dev) | 描边风格,质量高,MIT |
| Feather Icons | 简洁,经典 |
| Heroicons | 有描边和实心两套 |
| Remix Icon | 中文项目,图标多 |
| 自己画 | 完全可控 |
下载 SVG 后需要处理:
# 1. 去掉 width/height,保留 viewBox(才能缩放)
# 2. 把 fill="#000" 改成 fill="none" stroke="currentColor"
# 3. 去掉不必要的注释和 id
# 用 svgo 自动优化(很值得装)
npx svgo icon.svg -o icon.min.svg一个典型的处理结果:
<!-- 原始 -->
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="#000" stroke-width="2" stroke-linecap="round">
<circle cx="11" cy="11" r="7"/>
<path d="M20 20l-3.5-3.5"/>
</svg>
<!-- 改成 symbol -->
<symbol id="i-search" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round">
<circle cx="11" cy="11" r="7"/>
<path d="M20 20l-3.5-3.5"/>
</symbol>七、独立 SVG 文件 vs 内联
| 方式 | 优点 | 缺点 |
|---|---|---|
| 内联 symbol | 零请求,可用 CSS 控制 | HTML 变大(但可缓存) |
独立 .svg 文件 + <img> | 可缓存 | 不能用 CSS 改颜色 |
外部 symbol 文件 + <use href="icons.svg#id"> | 可缓存 | 多一次请求,跨域有限制 |
小图标集(十几个)→ 内联最佳;
大图标集(上百个)→ 外部文件 + <use>。
八、无障碍
<!-- 装饰性图标:对屏幕阅读器隐藏 -->
<svg class="icon" aria-hidden="true"><use href="#i-search"></use></svg>
<!-- 按钮里只有图标:必须给按钮加 aria-label -->
<button aria-label="切换主题">
<svg class="icon" aria-hidden="true"><use href="#i-sun"></use></svg>
</button>
<!-- 图标本身有含义(少见) -->
<svg role="img" aria-label="警告">...</svg>九、本站的做法
<!-- 搜索框 -->
<label class="search">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="11" cy="11" r="7"></circle>
<path d="M20 20l-3.5-3.5"></path>
</svg>
<input type="search" aria-label="搜索文章">
</label>主题按钮的图标由 JS 根据当前主题替换 innerHTML:
btn.innerHTML = t === "dark" ? sunSvg : moonSvg;只有三四个图标,直接内联比维护 symbol 表更简单。图标少的时候别过度设计。
十、检查清单
- 有 viewBox,无写死 width/height
- 用 currentColor(能跟随主题)
- 装饰图标有
aria-hidden="true" - 纯图标按钮有
aria-label - 描边风格参数统一
- 用 svgo 压缩过
SVG 图标的核心就一句:内联 + currentColor + viewBox。掌握这三点,不需要任何图标库。
