本文已参与「新人创作礼」活动,一起开启掘金创作之路。

在日常开发中,图标是大多数开发者都会有需求的,但在一个项目中,我们一般只引入一个 UI 组件库,如 ElementAntd ,组件库提供的图标往往是有限的,并不能满足所有的需要。所以在本文中,介绍如何基于 Iconify 二次封装一个 Icon 组件。

Iconify 可以访问 80 多个流行的开源图标集,其中有超过 5000 个图标可供选择,基本上能满足了我们项目中的所有需求了。在 Iconify 上,你可以查询到你想要的组件库图标并使用,如 ElementAntd

安装依赖

首先我们先来安装一下所需要的依赖

 1pnpm i @iconify/json @purge-icons/generated vite-plugin-purge-icons vite-plugin-svg-icons -D
 1pnpm i @iconify/iconify -S
依赖版本作用
@iconify/iconify2.2.1Iconify 核心库
@iconify/json2.1.28Iconify 所有图标集
@purge-icons/generated0.8.1提取使用的图标名称,然后将图标的数据 (SVG) 捆绑到您的代码中
vite-plugin-purge-icons0.8.1PurgeIcons 的 vite 插件
vite-plugin-svg-icons2.0.1用于本地生成 svg 雪碧图

配置

安装完成之后,在 vite.config.ts 中引入 vite-plugin-svg-iconsvite-plugin-purge-icons

 1import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
 2import PurgeIcons from 'vite-plugin-purge-icons'

然后在 plugins 字段中添加以下代码

 1createSvgIconsPlugin({
 2  
 3  iconDirs: [pathResolve('src/assets/svgs')],
 4  symbolId: 'icon-[dir]-[name]',
 5  svgoOptions: true
 6}),
 7PurgeIcons()

组件编写

配置好依赖后,接下来,我们就来讲解下如何基于 Iconify 封装 Icon 组件。

首先创建一个 Icon.vue 组件,初始结构如下

 1<script setup lang="ts">
 2</script>
 3
 4<template>
 5</template>
 6

由于我们项目中都会使用一个 UI 组件库,为了保证与该图标样式一致性,我们会基于该组件库原本的 Icon 组件稍微封装一下,这里我们以 element-plus 为例。

并且 Icon 组件 需要对外暴露三个参数 iconcolorsize,分别对应 图标名称图标颜色图标尺寸

 1<script setup lang="ts">
 2import { ElIcon } from 'element-plus'
 3
 4const props = defineProps({
 5  // icon name
 6  icon: {
 7    type: String,
 8    default: ''
 9  },
10  // icon color
11  color: {
12    type: String,
13    default: ''
14  },
15  // icon size
16  size: {
17    type: Number,
18    default: 16
19  }
20})
21</script>
22
23<template>
24</template>
25

本地图标

然后我们针对本地的 svg 进行一个处理。

在上一步中,我们通过了 createSvgIconsPlugin 生成了本地的 svg 雪碧图。所以我们在 Icon.vue 中需要添加判断,判断是否是本地图标,这里我们需要约定下规则,props.icon 传入的字符串,如果以 svg-icon: 开头的,代表的就是本地图标,反之,则是 Iconify 图标。

Icon.vue 中添加以下代码

 1<script setup lang="ts">
 2// ...
 3import { computed, unref } from 'vue'
 4
 5// 判断是否是本地图标
 6const isLocal = computed(() => props.icon.startsWith('svg-icon:'))
 7
 8// 如果是本地图标拆分出 svg-icon: 后面的字段
 9const symbolId = computed(() => {
10  return unref(isLocal) ? `#icon-${props.icon.split('svg-icon:')[1]}` : props.icon
11})
12
13// ...
14</script>
15
16<template>
17  <ElIcon :size="size" :color="color">
18    <svg v-if="isLocal" aria-hidden="true">
19      <use :xlink:href="symbolId" />
20    </svg>
21  </ElIcon>
22</template>

到这里,本地图标就可以使用并渲染了

 1<script setup lang="ts">
 2import Icon from '@/components/Icon.vue'
 3</script>
 4
 5<template>
 6  <div>
 7    <Icon icon="svg-icon:peoples" />
 8    <Icon icon="svg-icon:money" />
 9    <Icon icon="svg-icon:message" />
10    <Icon icon="svg-icon:shopping" />
11  </div>
12</template>
13

Iconify 图标

处理完本地 svg 图标后,我们还需要处理下 Iconify

Icon.vue 中添加以下代码

 1<script setup lang="ts">
 2// ...
 3import { computed, unref, ref, watch, nextTick } from 'vue'
 4import Iconify from '@purge-icons/generated'
 5
 6// ...
 7
 8const elRef = ref<ElRef>(null)
 9
10// 设置 Iconify 样式
11const getIconifyStyle = computed(() => {
12  const { color, size } = props
13  return {
14    fontSize: `${size}px`,
15    color
16  }
17})
18
19// 更新 Iconify
20const updateIcon = async (icon: string) => {
21  if (unref(isLocal)) return
22
23  const el = unref(elRef)
24  if (!el) return
25
26  await nextTick()
27
28  if (!icon) return
29
30  const svg = Iconify.renderSVG(icon, {})
31  if (svg) {
32    el.textContent = ''
33    el.appendChild(svg)
34  } else {
35    const span = document.createElement('span')
36    span.className = 'iconify'
37    span.dataset.icon = icon
38    el.textContent = ''
39    el.appendChild(span)
40  }
41}
42
43watch(
44  () => props.icon,
45  (icon: string) => {
46    updateIcon(icon)
47  }
48)
49</script>
50
51<template>
52  <ElIcon :class="prefixCls" :size="size" :color="color">
53    // ...
54    <span v-else ref="elRef" :class="$attrs.class" :style="getIconifyStyle">
55      <span class="iconify" :data-icon="symbolId"></span>
56    </span>
57  </ElIcon>
58</template>
59

由于我们 Iconify 的渲染方式是插入到元素中的,所以当 props.icon 变化的时候,是没办法监听到并重新渲染的,所以我们需要 watchprops.icon 的变化,并执行 updateIcon 函数重新渲染 Iconify

然后我们看下效果

 1<script setup lang="ts">
 2import Icon from '@/components/Icon.vue'
 3</script>
 4
 5<template>
 6  <div>
 7    <Icon icon="ep:aim" />
 8    <Icon icon="ep:alarm-clock" />
 9    <Icon icon="ep:baseball" />
10    <Icon icon="ep:chat-line-round" />
11  </div>
12</template>
13

这样我们就完成了 Iconify 图标的渲染了。

后续如果需要其他组件库的图标,可以在 Iconify 上搜索,然后复制想要的图标名称,传入到 Icon 组件即可。

文章相关代码,可查看 vue-element-plus-admin

个人笔记记录 2021 ~ 2025