SVG 图标组件:SVG 类型图标与 Iconfont
为什么需要自定义 SVG 图标
尽管 Element Plus、Naive UI、Ant Design Vue 等 UI 库都自带图标集,Iconify 也提供了海量图标,但在实际项目中仍需要自定义 SVG 图标的场景:
| 场景 | 说明 |
|---|---|
| UI 设计师出图 | 设计团队通过 Figma/Sketch 导出 SVG 图标 |
| Iconfont 平台 | 国内常用的图标管理平台,支持团队协作 |
| 品牌图标 | 企业专属 Logo 和品牌标识 |
| 第三方图标集 | 从其他图标网站购买的版权图标 |
Element Plus 的图标使用 JSX 语法,写法冗长:
<!-- Element Plus 图标写法 -->
<template>
<el-icon :size="20" color="#409EFC">
<ChatLineRound />
</el-icon>
</template>
<script setup>
import { ChatLineRound } from '@element-plus/icons-vue'
</script>
vue
当项目中有大量图标时,这种写法不够简洁,每个图标都需要单独 import 和注册。
Iconfont 平台使用流程
1. 搜索与收藏图标
在 iconfont.cn 上搜索并添加图标到购物车,然后添加到项目。
2. 批量下载 SVG
安装 Chrome 插件 "Iconfont 辅助工具合集",提供以下增强功能:
- 批量下载 SVG 文件(打包为 ZIP)
- 生成雪碧图 CSS
- 生成 TypeScript 类型定义
- 批量去色、锁定、删除等操作
3. 放置到项目目录
# 将下载的 SVG 文件放到 assets/icons 目录
src/assets/icons/
├── advice.svg
├── setting.svg
├── shell.svg
└── home.svg
bash
直接导入 SVG 的局限
<script setup lang="ts">
// Vite 原生支持 SVG 导入(作为 URL)
import svgUrl from '@/assets/icons/advice.svg'
</script>
<template>
<!-- 作为图片使用,无法控制颜色 -->
<img :src="svgUrl" class="text-red-500 text-3xl" />
</template>
vue
这种方式的问题:
- SVG 作为
<img>加载,无法通过 CSS 控制颜色 - 无法绑定 DOM 事件
- 尺寸控制不够灵活
text-red-500之类的颜色类不生效(因为是图片而非 SVG 内联)
vite-plugin-svg-icons 集成
vite-plugin-svg-icons 是专为 Vite 设计的 SVG 雪碧图插件,核心功能:
- 自动扫描:读取指定目录下所有 SVG 文件
- 生成雪碧图:将所有 SVG 合并为 SVG Symbol
- currentColor 替换:将 SVG 中的
fill/stroke替换为currentColor,使颜色可被 CSS 控制
安装
pnpm add vite-plugin-svg-icons -D
bash
Vite 配置
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import { resolve } from 'path'
export default defineConfig({
plugins: [
vue(),
createSvgIconsPlugin({
// 指定需要扫描的 SVG 图标目录
iconDirs: [resolve(process.cwd(), 'src/assets/icons')],
// 图标(symbol)的 ID 前缀,默认为 icon
symbolId: 'icon-[name]',
// 是否注入到 DOM 中,默认 true
inject: 'body-last',
// 自定义 DOM ID
customDomId: '__svg__icons__dom__',
}),
],
})
typescript
注册虚拟模块
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
// 注册 SVG 雪碧图虚拟模块(必须在 createApp 之前)
import 'virtual:svg-icons-register'
const app = createApp(App)
app.mount('#app')
typescript
创建 SvgIcon 组件
<!-- src/components/SvgIcon.vue -->
<script setup lang="ts">
interface SvgIconProps {
/** 图标名称(对应 SVG 文件名) */
type: string
/** 图标大小 */
size?: number | string
/** 图标颜色 */
color?: string
}
const props = withDefaults(defineProps<SvgIconProps>(), {
size: 16,
color: 'currentColor',
})
</script>
<template>
<svg
:width="size"
:height="size"
:style="{ color }"
class="inline-block align-middle fill-current"
aria-hidden="true"
>
<use :xlink:href="`#icon-${type}`" />
</svg>
</template>
vue
使用组件
<!-- src/views/Demo.vue -->
<script setup lang="ts">
import SvgIcon from '@/components/SvgIcon.vue'
</script>
<template>
<!-- 通过 class 控制颜色 -->
<SvgIcon type="advice" class="text-sky-500" :size="24" />
<!-- 通过 color 属性控制 -->
<SvgIcon type="setting" color="#f56c6c" :size="32" />
<!-- 通过 Tailwind 控制尺寸 -->
<SvgIcon type="shell" class="text-green-500 w-8 h-8" />
<!-- 可以绑定事件 -->
<SvgIcon
type="home"
class="text-yellow-500 cursor-pointer"
@click="handleClick"
/>
</template>
vue
插件核心原理
vite-plugin-svg-icons 在构建时做了以下处理:
输入: src/assets/icons/advice.svg
<svg fill="#333"><path d="..."/></svg>
处理:
1. 读取 SVG 内容
2. 替换 fill/stroke 属性值为 currentColor
3. 包裹为 <symbol id="icon-advice">...</symbol>
输出: 注入到 HTML body 尾部的 SVG 雪碧图
<svg xmlns="..." style="display:none">
<symbol id="icon-advice" viewBox="...">
<path fill="currentColor" d="..."/>
</symbol>
<symbol id="icon-setting" viewBox="...">
...
</symbol>
</svg>
text
使用时通过 <use xlink:href="#icon-advice" /> 引用对应的 symbol,由于 fill 被替换为 currentColor,CSS 设置的 color 值会自动应用到 SVG。
已知问题与注意事项
stroke 属性替换导致变形
部分 SVG 使用 stroke 描边绘制,插件将 stroke 的颜色值替换为 currentColor 时可能导致图标变形。这是该仓库的一个已知 issue,社区已提交 PR 但尚未合并。
解决方案:
- 在 SVG 源文件中手动将
stroke颜色改为currentColor - 使用社区修复版 fork:查看项目的 issues 和 PR 中提到的中间库
- 在
createSvgIconsPlugin中配置stroke替换规则
createSvgIconsPlugin({
iconDirs: [resolve(process.cwd(), 'src/assets/icons')],
symbolId: 'icon-[name]',
// 自定义 SVGO 配置,控制哪些属性被替换
svgoOptions: {
plugins: [
{
name: 'removeFillNone',
fn: () => ({
element: {
enter: (node) => {
// 保留特定的 fill 属性,避免变形
if (node.attributes.fill === 'none') {
delete node.attributes.fill
}
},
},
}),
},
],
},
})
typescript
方案对比
| 方案 | 颜色可控 | 事件绑定 | 按需加载 | 离线可用 | 适用场景 |
|---|---|---|---|---|---|
<img src="*.svg"> | 否 | 否 | 是 | 是 | 展示型图片 |
vite-plugin-svg-icons | 是 | 是 | 否(全量注入) | 是 | 自定义 SVG 图标集 |
Iconify @iconify/vue | 是 | 是 | 是 | 需配置 | 开源图标集 |
UnoCSS presetIcons | 是 | 是 | 是 | 是 | 开源图标集 |
| Iconfont Symbol 引用 | 是 | 是 | 是 | 是 | Iconfont 平台图标 |
提示:实际项目中可组合使用多种方案,如 Iconify 处理开源图标集,
vite-plugin-svg-icons处理设计师提供的自定义图标。
↑