最近看项目视频的时候对里面使用 svg 的方式感到很好奇,于是去网上查了一下,发现 svg 竟然也有类似于 css 雪碧图一样的用法,也就是 svg-sprite(孤陋寡闻了),而且配合插件后能够以组件化的方式使用 svg,非常方便。这里记录下一些相关用法。
视频里的做法是注册这样的一个全局组件:
// icon.vue
<svg>
<defs>
<symbol id="icon1">
<path></path>
</symbol>
<symbol id="icon2">
<path></path>
</symbol>
......
</defs>
</svg>
css 雪碧图中是把多个背景图片放在一张大的图片中,而 svg 雪碧图则是把多个 symbol
放在一个大的 svg 中,每个 symbol
代表了一个图标,以后每次想要使用图标,只需要写这么一段代码即可:
<svg>
<use :xlink:href="#icon1"></use>
</svg>
但是这里有两个问题:
- 从图标库(比如阿里的 iconfont)下载下来的通常是
.svg
文件,如何根据多个单独的.svg
文件生成 svg 雪碧图? - 每次要使用图标都得写这么一段代码,并不是很方便,是否可以像使用组件那样使用图标?
这里的关键是使用 svg-sprite-loader
这个插件。
安装插件
首先 npm 安装:
npm i svg-sprite-loader --save
接着我们用一个文件夹专门放各种需要用到的 .svg
文件,这里以 src/assets/img/icons
为例,从 iconfont 下载 .svg
文件后放到这个文件夹即可。
修改配置
vue-cli3 默认会通过 file-loader
对 .svg
文件进行处理,这里我们并不想让它处理我们的 .svg
图标文件,但是有的 .svg
文件又确确实实需要用它处理(总不可能所有的 svg 文件都用来做图标吧),所以我们要排除掉 file-loader
对 src/assets/img/icons
这个文件夹的处理。在 vue.config.js
的 module.exports
中新增:
module.exports = {
chainWebpack(config){
config.module
//排除对于 icons 目录中 svg 文件的处理
.rule('svg')
.exclude
.add('/src/assets/img/icons')
.end()
}
}
接着,指定 svg-sprite-loader
对图标文件夹里面的 .svg
文件进行处理:
module.exports = {
chainWebpack(config){
config.module
//排除对于 icons 目录中 svg 文件的处理
.rule('svg')
.exclude
.add('/src/assets/img/icons')
.end()
.end()
//设置 svg-sprite-loader 处理 icons 目录中的 svg
.rule('icons')
.test(/\.svg$/)
.include
.add(resolve('src/assets/img/icons'))
.end()
.use('svg-sprite-loader')
.loader("svg-sprite-loader")
.options({symbolId:'icon-[name]'})
}
}
这样其实已经可以生成 svg 雪碧图了,之后这个雪碧图会作为 svg
元素注入到 html
中:
接下来封装图标组件。
封装图标组件
在 components/common/icon
下新建一个 SvgIcon.vue
文件:
<template>
<svg :fill="iconColor" class="svg-icon">
<use :xlink:href="iconNameCp" />
</svg>
</template>
<script>
export default {
name: 'svgIcon',
props: {
// 图标类型
iconName: {
type: String,
required: true
},
// 图标颜色
iconColor:{
type:String,
default:'#666'
}
},
computed: {
iconNameCp() {
return `#icon-${this.iconName}`
}
}
}
</script>
使用组件的时候可以通过传值 iconName
和 iconColor
确定图标的类型和颜色。
全局注册组件
因为可能很多地方都会用到图标,这里选择全局注册 SvgIcon.vue
组件。在 src/assets/img/icons
文件夹下新建 index.js
文件:
// 全局引入 svgIcon 组件
import Vue from 'vue'
import SvgIcon from '@/components/common/icon/SvgIcon.vue'
Vue.component('svg-icon', SvgIcon)
// 让 icons/svg下面的图片自动导入,而不是每次手动导入
const req = require.context('./svg', false, /\.svg$/);
req.keys().map(req);
之后,在 main.js
中引入 index.js
文件:
import 'assets/img/icons'
使用组件
以后每次要使用图标就非常方便了,只需要一行代码即可:
<svg-icon class="icon" :icon-name="xxxxx" :icon-color="xxxxx"></svg-icon>
样式修改
从 iconfont 下载下来的图标文件默认没有内联的 fill
属性,所以可以像上面那样直接为 svg
元素指定 fill
属性,fill
会继承给子元素;如果下载的时候选择了颜色,就会多出来内联的 fill
属性,此时需要显式指定子元素的 fill
继承自父元素(否则继承的权重很低,样式无法被应用):
svg path {
fill:inherit
}
为什么这里不能写成下面这样呢?
.icon path {
fill:inherit
}
这是因为 svg->use
里面会生成一个 shadow dom,这个 shadow dom 包含了 svg->path
,它是无法通过 css 选择器拿到的,所以上面这个样式声明不会起效果。
当然还可以用 currentColor
修改图标颜色。因为在元素自身没有 color
属性的时候,它的 currentColor
会继承父元素的 color
属性,所以可以给 .icon
设置 color
,并指定每一个 path
的 fill
属性都是 currentColor
:
.icon {
color:#fff
}
svg path {
fill:currentColor
}
补充
iconfont 本身可以根据添加的图标自动生成 js 代码,之后只需将 js 文件引入项目中即可,这种方式同样可以将 svg
注入到 html
中:
但是这种方式不利于代码的维护,不可能说每一次新增图标都到 iconfont 重新生成一遍代码,再重新引入到项目中,这样太麻烦了。所以才使用了 svg-sprite-loader
插件,这样每次新增图标,只需要下载图标并放到对应文件夹即可。