1、背景
我们在做后台系统时,经常会遇到UI
给我们一引起svg
的图片,虽说svg
的图片,可以直接在代码中使用,但是却显得不那么优雅,且显得比较臃肿,那有没有好的解决方案呢?于是使用万能google
一搜,看了几篇文章,大致思路就是封装一个SvgIcon
组件,通过props
来接收svg
图片。这样做有很多好处:
- 图标易于实时修改
- 图标可以带动画
- 你可以使用标准的 prop 和默认值来将图标保持在一个典型的尺寸并随时按需改变它们
- 图标是内联的,所以不需要额外的 HTTP 请求
- 可以动态地使得图标可访问
2、思路
利用svg的symbol
元素,将每个icon包括在symbol
中,通过use
元素使用该symbol
.
那要封装一个SvgIcon
组件,那我们先考虑入参有哪些?经过思考,总结了有几个属性:
name:
必填
, Svg图标的名称
width: 选填,Svg图标的大小
height: 选填,Svg图标的高度
color: 选填,Svg图标的颜色
iconClass: 选填,Svg图标的样式类
封装SvgIcon
组件,主要是利用<svg>
元素与其子元素<use>
来实现,下面是取自MDN上的一段话:
use
元素在SVG文档内取得目标节点,并在别的地方复制它们。它的效果等同于这些节点被深克隆到一个不可见的DOM中,然后将其粘贴到use
元素的位置,很像HTML5中的克隆模板元素
我们的每一个icon都对应着一个symbol
元素,然后通过use
来引用symbol
:
<use xlink:href="#symbolId"></use>
那请问要在哪里去获取symbolId
,这时就要用到一个loader
,即svg-sprite-loader
。
首先安装:
yarn add svg-sprite-loader -D
svg-sprite-loader
会把我们的icon塞到一个个symbol
中,那要如何配置svg-sprite-loader
呢?
// vue.config.js
module.exports = {
chainWebpack: config => {
// 找到svg-loader
const svgRule = config.module.rule('svg')
// 清除已有的loader, 如果不这样做会添加在此loader之后
svgRule.uses.clear()
// 正则匹配排除node_modules目录
svgRule.exclude.add(/node_modules/)
svgRule.include.add(resolve('src/assets/icons'))
// 添加svg新的loader处理
svgRule
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.tap(options => {
options = {
symbolId: 'icon-[name]'
}
return options
})
}
}
另外,我们其他svg
图片,我们还是想当作图片使用:
// vue.config.js
module.exports = {
chainWebpack: config => {
// 找到svg-loader
const svgRule = config.module.rule('svg')
// 清除已有的loader, 如果不这样做会添加在此loader之后
svgRule.uses.clear()
// 正则匹配排除node_modules目录
svgRule.exclude.add(/node_modules/)
svgRule.include.add(resolve('src/assets/icons'))
// 添加svg新的loader处理
svgRule
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.tap(options => {
options = {
symbolId: 'icon-[name]'
}
return options
})
// 修改images loader 添加svg处理
const imagesRule = config.module.rule('images')
imagesRule.exclude.add(resolve('src/assets/icons'))
config.module
.rule('images')
.test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
}
}
有了配置,那我们就可以开始动手写我们的组件了。
// components/SvgIcon/index.vue
<template>
<svg
:class="svgClass"
aria-hidden="true"
:width="width"
:height="height"
:fill="color"
>
<use :xlink:href="iconName" />
</svg>
</template>
<script>
export default {
name: 'SvgIcon',
props: {
/* SVG图标名称 */
name: {
type: String,
required: true
},
/* 样式类 */
className: {
type: String,
default: ''
},
/* SVG图标宽度大小 */
width: {
type: [String, Number],
default: 16
},
/* SVG图标高度大小 */
height: {
type: [String, Number],
default: 16
},
/* SVG图标颜色 */
color: {
type: String,
default: 'currentColor'
}
},
computed: {
iconName() {
return `#icon-${this.name}`
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
} else {
return 'svg-icon'
}
}
}
}
</script>
<style>
.svg-icon {
vertical-align: -0.15em;
overflow: hidden;
}
</style>
另外在assets/icons
中存放我们要做为icon
的svg
图片,如下图所示:
紧接着,我们写一个自动导入的方法,避免手动引入svg
。
// @/core/icons.js
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'// svg组件
// register globally
Vue.component('SvgIcon', SvgIcon)
const requireAll = requireContext => requireContext.keys().map(requireContext)
const req = require.context('@/assets/icons/svg', false, /\.svg$/)
requireAll(req)
在main.js
中引入icons.js
:
import Vue from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import router from './router'
import store from './store'
import './styles/index.scss'
// 引入icons
import '@/core/icons'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
最后我们找个文件检验下成果:
<svg-icon name="iconxiaoshouguanli1" color="blue" width="30" height="24"></svg-icon>
换个大小试试:
<svg-icon name="iconxiaoshouguanli1" color="blue" width="48" height="48"></svg-icon>
可以看到图标明显变大了!
再换个颜色试下:
<svg-icon name="iconxiaoshouguanli1" color="red" width="48" height="48"></svg-icon>
颜色也生效了。
总结
-
原理:
symbol
+use:xlink:href
;svg-sprite-loader
生成雪碧图;require.context
动态引入所有文件;
-
优化SVG
require.context
API.