SVG 图标方案:不引图标库也能有成套图标

图标库动辄几百 KB,而你只需要十几个图标。用 SVG symbol,几 KB 搞定,还能随主题变色。

一、三种方案对比

方案体积可控性缺点
图标字体(Font Awesome)大(整包)只能改颜色和大小加载失败显示方框、有 FOUC
图标库组件(react-icons 等)按需引入还行引入依赖
内联 SVG symbol极小完全可控要自己收集 SVG

本站只有几个图标(搜索、太阳/月亮、箭头),全部内联,零额外请求。

二、symbol 雪碧图

在 HTML 里放一个隐藏的 SVG,里面定义多个 <symbol>

html
<!-- 放在 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>

使用:

html
<svg class="icon"><use href="#i-search"></use></svg>

三、currentColor:自动跟随文字颜色

这是 SVG 图标相对图标字体最大的优势:

html
<symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  <!-- stroke="currentColor" 让描边用继承来的 color -->
</symbol>
css
.btn { color: #333; }
.btn:hover { color: #0066ff; }   /* 图标自动跟着变 */

.icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.125em;    /* 与文字基线对齐 */
  fill: none;
}

主题切换时图标自动变色,不需要维护两套资源。本站的搜索图标和主题按钮就是这么做的。

四、尺寸控制

css
.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 里不要混用,视觉会不统一。

bash
# 描边风格统一参数(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 后需要处理:

bash
# 1. 去掉 width/height,保留 viewBox(才能缩放)
# 2. 把 fill="#000" 改成 fill="none" stroke="currentColor"
# 3. 去掉不必要的注释和 id

# 用 svgo 自动优化(很值得装)
npx svgo icon.svg -o icon.min.svg

一个典型的处理结果:

html
<!-- 原始 -->
<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>

八、无障碍

html
<!-- 装饰性图标:对屏幕阅读器隐藏 -->
<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>

九、本站的做法

html
<!-- 搜索框 -->
<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

js
btn.innerHTML = t === "dark" ? sunSvg : moonSvg;

只有三四个图标,直接内联比维护 symbol 表更简单。图标少的时候别过度设计。

十、检查清单

  • 有 viewBox,无写死 width/height
  • 用 currentColor(能跟随主题)
  • 装饰图标有 aria-hidden="true"
  • 纯图标按钮有 aria-label
  • 描边风格参数统一
  • 用 svgo 压缩过

SVG 图标的核心就一句:内联 + currentColor + viewBox。掌握这三点,不需要任何图标库。