Why ? 🤔

为什么使用 SVG 而不是 IconFont,可以看这篇文章 Inline SVG vs Icon Fonts 

https://css-tricks.com/icon-fonts-vs-svg/
IconFont
  1. 浏览器将其视为文字进行抗锯齿优化,有时得到的效果并没有想象中那么锐利。 尤其是在不同系统下对文字进行抗锯齿的算法不同,可能会导致显示效果不同
  2. IconFont 作为一种字体,Icon 显示的大小和位置可能要受到 font-sizeline-heightword-spacing  等等 CSS 属性的影响。 Icon 所在容器的 `CSS` 样式可能对 `Icon` 的位置产生影响,调整起来很不方便
  3. 使用上存在不便。首先,加载一个包含数百个图标的IconFont,却只使用其中几个,非常浪费加载时间。 自己制作IconFont以及把多个IconFont 中用到的图标整合成一个 Font 也非常不方便
  4. 如果想实现最大程度的浏览器支持,可能要提供至少四种不同类型的字体文件。包括TTFWOFFEOT以及一个使用SVG格式定义的字体
  5. 网络延时会导致 Icon 会先加载出来一个 string
Svg Icon
  1. 完全离线化使用,不需要从 CDN 下载字体文件,图标不会因为网络问题呈现方块,也无需字体文件本地部署
  2. 在低端设备上 SVG 有更好的清晰度
  3. 支持多色图标
  4. 对于内建图标的更换可以提供更多 API,而不需要进行样式覆盖

劣势:兼容性(其实目前浏览器兼容性已经不错 查看兼容性


What ? 🧐
实现原理
  • svg 图标比较小,而且都是可读的 xml 文本,我们把它直接放在项目中即可,通过vite-plugin-svg-icons 插件生成 svg 雪碧图,实现自动引入
  • 插件会自动将所有 svg 图片加载到 HTML 中。并且每一个 svg 将会被过滤去无用的信息数据。让 svg 达到最小的值。之后使用 svg 图片就只需要操作 DOM 即可,而不需要发送 http 请求
  • 利用 svg 的 symbol 元素,将每个 icon 包括在 symbol 中
  • 再通过 <use xlink:href="symbolId"/> 来使用所需的 icon
优点:
  • 解决各种版本 iconfont 私有图标库问题
  • 每个 SVG 图标都可以更改大小颜色
  • 在页面中使用<svgIcon name="home" />,代码清爽

How ? 😉
  1. 配置 vite.config.ts 文件

    安装 vite-plugin-svg-icons 插件后编辑 Vite 配置文件:文档

    • iconDirs 项目中 svg 图标存放路径,这里设置为 assets 下的 icons 文件夹
    • symbolId 图标的唯一 id 标识,dir 代表图标的文件夹名称
    • 比如在 icons 中将登录模块相关的图标都整理到 login 文件夹中,则这里的 dir 就是 login
    import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' // 省略其他部分代码 plugins: [ createSvgIconsPlugin({ iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')], // 图标 ID 样式 dir 代表文件的文件夹名称 symbolId: 'svg-icon-[dir]-[name]', }), ]

    虽然用文件夹来区分已经可以很大程度避免重名问题了,但是也会出现iconDirs包含多个文件夹,且文件名一样的 svg,这个需要开发者自己规避下。

  2. 配置 main.ts
    • import 'virtual:svg-icons-register'
  3. 封装 SvgIcon 图标组件
    <template> <div :class="wrapperColor"> <svg aria-hidden="true" :width="width || size" :height="height || size" :class="className"> <use :xlink:href="symbolId" :fill="color" /> </svg> </div> </template> <script setup lang="ts" name="SvgIcon"> interface Props { prefix?: string name?: string color?: string size?: number width?: number height?: number className?: string dir?: string } const props = withDefaults(defineProps<Props>(), { prefix: 'svg-icon', name: '', color: '#fff', size: 16, width: 16, height: 16, className: 'svg-icon', dir: '', }) const wrapperColor = computed(() => `text-color-[${props.color}]`) const symbolId = computed(() => { return props.dir?.length ? `#${props.prefix}-${props.dir}-${props.name}` : `#${props.prefix}-${props.name}` }) </script> <style lang="scss" scoped> .svg-icon { vertical-align: -0.15em; fill: currentColor; overflow: hidden; } </style>
    • 通过 wrapperColor (Windi Css 功能类)将颜色设置给 svg 文件,默认白色
  4. 使用说明
    <svg-icon name="user" color="#2395f1" :size="30"/> <svg-icon name="action" color="#d9363e" :width="40" :height="30"/> <div class="text-green-500"> 文本 <svg-icon name="robot" dir="timeline" className="pr-10px" @click="test"/> <svg-icon name="action" color="#d9363e" /> </div>
    • name 就是放置在 @/assets/icons 文件夹里的图标文件名
    • color 颜色填充,使用此项会默认覆盖图标颜色
    • size 为图标的宽高值,也可以单独声明宽高的值,优先级更高
    • 如果想实现多个图标批量设置色值,可以通过给父元素设置 color 色值实现,不过优先级比单独给组件设置 color 要低
    • 如果图标是放在指定的文件夹内,需要使用 dir 标识
    • 图标的其他样式,可以通过额外设置 className 实现
    • 因为图标组件中是用 div 标签包裹 svg 的,可以直接给组件绑定事件

    注意事项: 🚨

    • 单色图标需要设计同学导出 fill ="currentColor" 属性的 svg 图片才能实现自定义颜色
    • 如果 UI 没有提供,可以在 Figma中安装 Svg Export 插件,将导出配置中的 Use currentColor as fillRemove all fills 选项打开