# svg 图标的组件化使用
正常情况下,svg 图标的体验式使用大概率是直接放在 dom 结构中即可。体验很棒,但是如果项目中真的这么使用,当一个图标在多处使用时,那源码结构会显得很臃肿。毕竟一个 svg 的路径信息大概时这样:
![](/assets/img/1.f6415a5c.png)
# Svg Sprite
看到 sprite 有没有很眼熟?没错,有点像“远古”开发时使用的 css雪碧图(css sprite)。
Svg Sprite 的使用有两大标志:symbol 标签 和 use 标签
# symbol 标签
symbol 标签 可以理解成 Svg Sprite 的组件,symbol 元素将 svg 的路径信息进行包裹,并为每一个 symbol 元素设置唯一的 id。然后将这些 symbol 元素统一放在一个 svg 标签中进行包裹管理。
<svg>
<symbol id="symbol标签唯一id" viewbox="xxx">
<!-- svg 图标路径信息 -->
</symbol>
<symbol id="symbol标签唯一id" viewbox="xxx">
<!-- svg 图标路径信息 -->
</symbol>
<symbol id="symbol标签唯一id" viewbox="xxx">
<!-- svg 图标路径信息 -->
</symbol>
</svg>
# use 标签
use 标签 负责调用需要使用的 svg 图标。在需要使用相关图标的地方使用 svg 包裹 use 标签,然后为 use 标签设置 symbol 的 id 属性即可,并且可以在多处重复调用。
比如可以直接在 .html 文件的body中这样:
<svg>
<symbol id="user">
<path d="M63.444 64.996c20.633 0 37.359-14.308 37.359-31.953 0-17.649-16.726-31.952-37.359-31.952-20.631 0-37.36 14.303-37.358 31.952 0 17.645 16.727 31.953 37.359 31.953zM80.57 75.65H49.434c-26.652 0-48.26 18.477-48.26 41.27v2.664c0 9.316 21.608 9.325 48.26 9.325H80.57c26.649 0 48.256-.344 48.256-9.325v-2.663c0-22.794-21.605-41.271-48.256-41.271z" stroke="#979797"/>
</symbol>
<use xlink:href="#user" x="10" y="50" />
<use xlink:href="#user" x="130" y="50" />
</svg>
打开html文件,页面上就会有两个 user 图标,如下:
![](/assets/img/2.84d545d8.png)
更牛掰的是,use 标签还可以跨 svg 元素进行调用,形式类似于这样:
<!-- 有一个地方定义了 svg 的信息 -->
<svg>
<symbol id="symbol-1">
<!-- svg 图标路径信息1 -->
</symbol>
<symbol id="symbol-2">
<!-- svg 图标路径信息2 -->
</symbol>
</svg>
<!-- 有一个地方想使用定义的svg -->
<svg>
<use xlink:href="#symbol-X"></use>
</svg>
上述的使用方式,只要包裹 symbol 标签的 svg 被放在 body 中,在文档中的任何地方都可以通过 use 标签调用对应的 symbol 唯一 id 标识进行图标使用。
# Svg 图标组件化流程
经过上述对 svg 图标使用的了解,接下来就可以开始进行 vue 组件化封装的处理了。
先来梳理一下有哪些工作要做?
- svg 图标封装的组件使用形式是什么样子?需要的 props 参数有哪些?
- svg 图标如何存放?如何引入到开发环境中?
- 我们如何将引入的 svg 图标一一进行 symbol 标签的包裹并给予唯一的id,最后再统一放在一个 svg 标签中放入body下?
# 组件的形式与参数
<svg-icon icon="XXX" clasName="XXX" />
Props 名称 | 类型 | 必填 | 备注 |
---|---|---|---|
icon | String | 是 | svg 图标symbol id |
className | String | 否 | 调整图标样式的class类名 |
# svg-icon.vue 组件
<template>
<svg class="svg-icon" :class="className" aria-hidden="true">
<use :xlink:href="iconName" />
</svg>
</template>
<script setup>
import { defineProps, computed } from 'vue'
const props = defineProps({
// icon 图标
icon: {
type: String,
required: true
},
// 图标类名
className: {
type: String,
default: ''
}
})
// 图标名称经过 svg-sprite-loader 处理后统一名称形式为 icon-文件名
const iconName = computed(() => `#icon-${props.icon}`)
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
.svg-external-icon {
background-color: currentColor;
mask-size: cover !important;
display: inline-block;
}
</style>
# svg 图标统一引入
将从设计师哪里得来的 svg 图标放在项目中,一般我们会默认放在 vue-cli 项目中约定的静态资源文件夹 assets 中,假设当前是在 src/assets/icons/svg 中。
我们需要做的是将 svg 文件统一引入到项目的上下文环境中,而 webpack 的 require.context API 恰好提供了该功能。
在 svg 文件同级的目录中创建 index.js 做 svg 图标的统一引入,src/assets/icons 目录下的文件结构如下:
.
├── index.js # 这里做 svg 文件的统一引入
└── svg # svg 图标文件夹
├── eye-open.svg
├── eye.svg
└── user.svg
└── ...(更多svg)
# icons/index.js
import SvgIcon from '@/components/svg-icon.vue'
// https://webpack.docschina.org/guides/dependency-management/#requirecontext
// 通过 require.context() 函数来创建自己的 context
const svgRequire = require.context('./svg', false, /\.svg$/)
// 此时返回一个 require 的函数,可以接受一个 request 的参数,用于 require 的导入。
// 该函数提供了三个属性,可以通过 require.keys() 获取到所有的 svg 图标
// 遍历图标,把图标作为 request 传入到 require 导入函数中,完成本地 svg 图标的导入
svgRequire.keys().forEach((path) => svgRequire(path))
export default (app) => {
app.component('svg-icon', SvgIcon)
}
icons/index.js
其实做了两件事:
- 通过
require.context()
统一将 svg 图标模块的导入到本地,请参考 require.context函数。 - 通过
app.component()
将先前的 svg-icon 组件注册成了全局组件
# main.js
在 项目入口文件 main.js 中按照 element-plus 引入的方式,导入 svg icon 的模块引用。
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
// vue add element-plus 自动添加
import installElementPlus from './plugins/element'
// 导入 svgIcon
import installIcons from '@/assets/icons'
const app = createApp(App)
// vue add element-plus 自动添加
installElementPlus(app)
// 添加 SvgIcon
installIcons(app)
app.use(store).use(router).mount('#app')
# svg-sprite-loader 的整合
上一步通过 webpack 的 require.context() 方法只是将 svg 图标导入到了本地。但针对 .svg 结尾的文件还需要做进一步的webpack配置处理。
svg-sprite-loader 的作用就是实现工作中的第三步,它会将统一引入的 svg 使用 symbol 标签包裹,并赋予唯一id。然后统一整合到 svg 标签中,同时把最大的 svg 标签放入到页面的 body 中供后续通过 use 标签使用。
svg-sprite-loader 的处理需要调整 webpack 配置。
注意:如果没有对特别指定 symbol 的 id 值,图标默认就是文件名称
以 vue3 为例,对 vue3 的webpack 配置要放在根目录中的 vue.config.js 文件中。下面的配置可以说是一个通用的配置文案,做一个ctrl + c
和 ctrl + v
程序员就好。
const path = require('path')
function resolve(dir) {
return path.resolve(__dirname, dir)
}
module.exports = {
chainWebpack(config) {
config.module
.rule('svg') // 获取到原来针对svg的规则
.exclude.add(resolve('src/assets/icons/svg')) // 将改规则排除 src/assets/icons 目录下的svg文件
.end() // 结束当前规则的调整
config.module
.rule('icons') // 重新配置一个新的规则名字 icons
.test(/\.svg$/) // 匹配以 .svg 为结尾的图标
.include.add(resolve('src/assets/icons/svg')) // 针对的目录是 src/assets/icons/svg
.end() // 结束新规则配置
.use('svg-sprite-loader') // 针对以上规则使用 loader
.loader('svg-sprite-loader') // 加载 loader
.options({
symbolId: 'icon-[name]' // 唯一id标识符配置,这个 icon-xxx 的形式,是在组件注册时针对 svg 图标配置的
})
.end()
}
}
最后我们可以在页面中看到如下情况,在 vue 编译的dom节点 #app
外部,放入了一个 svg 包裹的所有 svg 图标的整合块,每个 svg 图标被 symbol 标签包裹,唯一 id 正式我们设置的 #icon-文件名
的形式。
![](/assets/img/3.f8ef5420.png)
![](/assets/img/4.b7cddd84.png)