如何在Vue项目中使用SVG Icon
Web中对于Icon的使用已经是非常频繁的一件事情了,而且很有图标的使用会让你的Web应用程序或Web网页面变得更具可交互性和可使用性。早前在《Web中的图标》一文中和大家一起探讨了如何在Web中使用图标。其中不同的使用方式都具有各自的优势,但随着技术的革新,其中SVG的图标在Web中的使用也越来越频繁,并且其具备的优势也越来越明显。正因如此,在自己的项目中使用SVG的图标的场景也越来越多,因此,今天想和大家一起聊聊如何在Vue的项目中更好的使用SVG图标。
单个Icon组件的使用
在Vue官方网站专门有一节内容,向大家介绍了如何在项目中更好的使用SVG图标系统,而且这个系统是可具编辑的。先来看最基础的一个示例,这个示例是基于Vue CLI3的基础上构建的。通过官网提供的命令,使用CLI命令创建一个Vue项目,比如:
vue create svg-icon-app
采用默认的模板来构建了一个svg-icon-app
项目。我们先跟着官网提供的思路来一步一步往下走,在components
目录下创建一个新的目录icons
。并将相应的SVG图标以一种标准化的方式命名,比如:
components/icons/IconBox.vue
components/icons/Facebook.vue
components/icons/Twitter.vue
components/icons/GooglePlus.vue
其中IconBox.vue
是SVG图标的一个基础图标组件,在这个组件中使用了slot
,其模板如下:
<!-- IconBox.vue -->
<template>
<svg xmlns="http://www.w3.org/2000/svg"
:width="width"
:height="height"
viewBox="0 0 18 18"
:aria-labelledby="iconName"
role="presentation"
>
<title
:id="iconName"
lang="en"
>{{ iconName }} icon</title>
<g :fill="iconColor">
<slot />
</g>
</svg>
</template>
你可以像上面这样使用这个基础图标,唯一可能要做的就是根据你图标的viewBox
来更新其viewBox
。在基础图标组件里会有width
、height
、iconColor
以及iconName
的props
,这样我们就可以通过props
对其动态更新。iconName
将会同时用在<title>
的内容及其用于提供可访问性的id
上。
<!-- IconBox.vue -->
<script>
export default {
name: 'IconBox',
props: {
iconName: {
type: String,
default: 'box'
},
width: {
type: [Number, String],
default: 32
},
height: {
type: [Number, String],
default: 32
},
iconColor: {
type: String,
default: 'currentColor'
}
},
data () {
return {
}
}
}
</script>
currentColor
会成为fill
的默认值,于是图标就会继承color
的值(currentColor
是CSS的一个很优秀的属性)。我们也可以根据需求传递一个不一样的颜色值。
特别声明,
viewBox
是SVG中的一个重要的概念,如果你从未接触过的话,建议你花点时间对该概念进行了解。你可以点击这里、或这里、还有这里进行了解。
IconBox
组件完成了之后,接下来把对应的图标组件完善。
示例中的几个图标,都来自于Font Awesome提供的SVG图标。
除此之外,还可以通过iconmonstr、IcoMoon平台,甚至还可以使用NucleoApp来获取SVG图标。特别是NucleoApp来管理你的SVG图标:
我们通过编辑器打开其中一个.svg
文件,比如facebook.svg
,其代码会是这样的:
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="facebook-square" class="svg-inline--fa fa-facebook-square fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path d="M29,0 L3,0 C1.35,0 0,1.35 0,3 L0,29 C0,30.65 1.35,32 3,32 L16,32 L16,18 L12,18 L12,14 L16,14 L16,12 C16,8.69375 18.69375,6 22,6 L26,6 L26,10 L22,10 C20.9,10 20,10.9 20,12 L20,14 L26,14 L25,18 L20,18 L20,32 L29,32 C30.65,32 32,30.65 32,29 L32,3 C32,1.35 30.65,0 29,0 Z" id="Path"></path>
</svg>
我们只需要将其中<path>
部分代码放到图标组件的<template>
中,比如facebook.svg
的放到对应的Fackbook.vue
组件:
<!-- Facebook.vue -->
<template>
<path d="M29,0 L3,0 C1.35,0 0,1.35 0,3 L0,29 C0,30.65 1.35,32 3,32 L16,32 L16,18 L12,18 L12,14 L16,14 L16,12 C16,8.69375 18.69375,6 22,6 L26,6 L26,10 L22,10 C20.9,10 20,10.9 20,12 L20,14 L26,14 L25,18 L20,18 L20,32 L29,32 C30.65,32 32,30.65 32,29 L32,3 C32,1.35 30.65,0 29,0 Z" id="Path"></path>
</template>
<script>
export default {
name:'Facebook'
}
</script>
对于GooglePlus.vue
和Twitter.vue
组件类似。
为了更好的演示官网提供的示例,将在components
目录中新创建一个.vue
文件,将引用我们创建的Facebook
、Twitter
和GooglePlus
组件。该文件名暂时取名为VueOfficialSvgIcons.vue
,接下来我们就可以这样使用这几个SVG图标:
<!-- VueOfficialSvgIcons.vue -->
<template>
<div class="icons">
<IconBox icon-name="facebook"><Facebook /></IconBox>
<IconBox icon-name="twitter"><Twitter /></IconBox>
<IconBox icon-name="google plus"><GooglePlus /></IconBox>
</div>
</template>
<script>
import IconBox from './icons/IconBox'
import Facebook from './icons/Facebook'
import Twitter from './icons/Twitter'
import GooglePlus from './icons/GooglePlus'
export default {
name: 'VueOfficialSvgIcons',
components: {
IconBox,
Facebook,
Twitter,
GooglePlus
}
}
</script>
现在看到的效果,都是默认的尺寸32px x 32px
。
如果我们需要多种尺寸的图标,可以像下面这样做:
<!-- VueOfficialSvgIcons.vue -->
<IconBox
width="16"
height="16"
icon-name="facebook"><Facebook /></IconBox>
<IconBox icon-name="twitter"><Twitter /></IconBox>
<IconBox
width="48"
height="48"
icon-name="google plus"><GooglePlus /></IconBox>
得到的效果如下:
如果你需要不同的图标颜色,可以通过iconColor
传一个图标的颜色:
<!-- VueOfficialSvgIcons.vue -->
<IconBox
icon-color="#f36"
icon-name="facebook"><Facebook /></IconBox>
<IconBox
icon-color="#f0987a"
icon-name="twitter"><Twitter /></IconBox>
<IconBox icon-name="google plus"><GooglePlus /></IconBox>
效果如果如下:
这是Vue项目中使用SVG图标最基本的方式。通过上面的示例我们可以看出来,如果你还需要更多的控制SVG图标,可以在IconBox
基本组件中的props
设置更多的属性。在使用的时候按需传入即可。
SVG Sprite的使用
上面这种方式,每个Icon都需要一个独立的组件。如果你的Web应用有许多图标用在不同的地方时,这种方式较为适合。如果你只在一个页面上重复使用相同图标多次,那么使用SVG Sprite更为理想。
有关于SVG Sprites的使用可以阅读《SVG Sprite》一文。
简单地说,可以通过SVG的<symbol>
标签把所以SVG图标合并在一起,然后使用<use>
标签来调用<symbol>
中指定的图标。即 <use>
中的xlink:href
属性值与<symbol>
标签指定的id
相同。
在这里,我们来看看在Vue中怎么使用SVG Sprites来创建图标。基于上面的示例,我们先创建一个名为Icon.vue
组件:
<!-- Icon.vue -->
<template>
<svg :width="width" :height="height" :style="{color: iconColor}">
<use xmlns:xlink="http://www.w3.org/1999/xlink" :xlink:href="iconId"></use>
</svg>
</template>
<script>
export default {
name: 'Icon',
props: {
iconName: {
type: String,
default: 'box'
},
width: {
type: [Number, String],
default: 32
},
height: {
type: [Number, String],
default: 32
},
iconColor: {
type: String,
default: 'currentColor'
}
},
computed: {
iconId() {
return `#icon-${this.iconName}`
}
}
}
</script>
现在Icon
组件是功能性的了,我们需要在src/mail.js
中注册到Vue
实例中:
<!-- src/main.js -->
import iconComponent from './components/svgSprites/Icon'
Vue.component('icon', iconComponent)
为了区分前面的示例,这里为SVG Sprites单独创建一个独立的组件,比如SvgSpritesIcon.vue
组件,然后再这个组件中调用:
<icon icon-name="facebook"></icon>
<icon icon-name="twitter"></icon>
<icon icon-name="google"></icon>
但在页面中并不会看到有任何的Icon。这主要是因为我们还没有创建SVG Sprites。暂时我们人肉创建一个SVG Sprites,比如在App.vue
文件中创建一个SVG Sprites:
<!-- App.vue -->
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol viewBox="0 0 32 32" id="icon-facebook" fill="currentColor">
<path d="M29,0 L3,0 C1.35,0 0,1.35 0,3 L0,29 C0,30.65 1.35,32 3,32 L16,32 L16,18 L12,18 L12,14 L16,14 L16,12 C16,8.69375 18.69375,6 22,6 L26,6 L26,10 L22,10 C20.9,10 20,10.9 20,12 L20,14 L26,14 L25,18 L20,18 L20,32 L29,32 C30.65,32 32,30.65 32,29 L32,3 C32,1.35 30.65,0 29,0 Z"></path>
</symbol>
<symbol viewBox="0 0 32 32" id="icon-twitter" fill="currentColor">
<path d="M28.5714286,0 L3.42857143,0 C1.53571429,0 0,1.53571429 0,3.42857143 L0,28.5714286 C0,30.4642857 1.53571429,32 3.42857143,32 L28.5714286,32 C30.4642857,32 32,30.4642857 32,28.5714286 L32,3.42857143 C32,1.53571429 30.4642857,0 28.5714286,0 Z M25.0785714,11.3428571 C25.0928571,11.5428571 25.0928571,11.75 25.0928571,11.95 C25.0928571,18.1428571 20.3785714,25.2785714 11.7642857,25.2785714 C9.10714286,25.2785714 6.64285714,24.5071429 4.57142857,23.1785714 C4.95,23.2214286 5.31428571,23.2357143 5.7,23.2357143 C7.89285714,23.2357143 9.90714286,22.4928571 11.5142857,21.2357143 C9.45714286,21.1928571 7.72857143,19.8428571 7.13571429,17.9857143 C7.85714286,18.0928571 8.50714286,18.0928571 9.25,17.9 C7.10714286,17.4642857 5.5,15.5785714 5.5,13.3 L5.5,13.2428571 C6.12142857,13.5928571 6.85,13.8071429 7.61428571,13.8357143 C6.30917726,12.9675977 5.52600528,11.5031734 5.52857143,9.93571429 C5.52857143,9.06428571 5.75714286,8.26428571 6.16428571,7.57142857 C8.47142857,10.4142857 11.9357143,12.2714286 15.8214286,12.4714286 C15.1571429,9.29285714 17.5357143,6.71428571 20.3928571,6.71428571 C21.7428571,6.71428571 22.9571429,7.27857143 23.8142857,8.19285714 C24.8714286,7.99285714 25.8857143,7.6 26.7857143,7.06428571 C26.4357143,8.15 25.7,9.06428571 24.7285714,9.64285714 C25.6714286,9.54285714 26.5857143,9.27857143 27.4285714,8.91428571 C26.7928571,9.85 25.9928571,10.6785714 25.0785714,11.3428571 Z"></path>
</symbol>
<symbol viewBox="0 0 32 32" id="icon-google" fill="currentColor">
<path d="M28.5714286,0 L3.42857143,0 C1.53571429,0 0,1.53571429 0,3.42857143 L0,28.5714286 C0,30.4642857 1.53571429,32 3.42857143,32 L28.5714286,32 C30.4642857,32 32,30.4642857 32,28.5714286 L32,3.42857143 C32,1.53571429 30.4642857,0 28.5714286,0 Z M11.7142857,23.1428571 C7.76428571,23.1428571 4.57142857,19.95 4.57142857,16 C4.57142857,12.05 7.76428571,8.85714286 11.7142857,8.85714286 C13.6428571,8.85714286 15.25,9.55714286 16.5,10.7285714 L14.5642857,12.5928571 C14.0357143,12.0857143 13.1142857,11.4928571 11.7214286,11.4928571 C9.28571429,11.4928571 7.3,13.5071429 7.3,16.0071429 C7.3,18.5 9.28571429,20.5214286 11.7214286,20.5214286 C14.55,20.5214286 15.6071429,18.4857143 15.7785714,17.4428571 L11.7142857,17.4428571 L11.7142857,14.9857143 L18.4571429,14.9857143 C18.5285714,15.3428571 18.5714286,15.7071429 18.5714286,16.1714286 C18.5714286,20.25 15.8357143,23.1428571 11.7142857,23.1428571 Z M27.4285714,17.3 L25.3571429,17.3 L25.3571429,19.3714286 L23.2714286,19.3714286 L23.2714286,17.3 L21.2,17.3 L21.2,15.2142857 L23.2714286,15.2142857 L23.2714286,13.1428571 L25.3571429,13.1428571 L25.3571429,15.2142857 L27.4285714,15.2142857 L27.4285714,17.3 Z"></path>
</symbol>
</svg>
此时,你刷新你的页面,可以看到图标,如下图所示:
和前面的示例一样,我们可以通过props
中的width
、height
和iconColor
来设置图标的大小和颜色,比如:
<icon icon-name="facebook"></icon>
<icon icon-name="twitter" width="48" height="48"></icon>
<icon icon-name="google" width="64" height="64" icon-color="green"></icon>
此时的效果如下:
上面的示例中,虽然达到我们想要的效果。但SVG Sprite是我们人肉生成的。其实,我们可以借助相关的工具链,自动生成SVG Sprite。比如:
这些工具会在编译时打包 SVG,但是在运行时编辑它们会有一些麻烦,因为 <use>
标签在处理一些复杂的事情时存在浏览器兼容问题。同时它们会给你两个嵌套的 viewBox
属性,这是两套坐标系。所以实现上稍微复杂了一些。
接下来我们以svg-sprite-loader
为例,看看怎么自动生成SVG Sprite。我先从网上下载几个SVG的图标放在项目src/assets/icons/
下:
注意,我们的环境是Vue Cli3下。
先进行安装:
npm i svg-sprite-loader -D
现在我们需要做一些配置。Vue Cli内部是利用webpack-chain
插件来修改Webpack配置的。所以我们需要在项目根目录下创建一个Vue.config.js
文件,在该文件中利用webpack-chain
来修改相关的Webpack配置。比如:
// Vue.config.js
const path = require('path')
function resolve(dir) {
return path.join(__dirname, '.', dir)
}
module.exports = {
chainWebpack: config => {
config.module.rules.delete('svg')
config.module
.rule('svg-sprite-loader')
.test(/\.svg$/)
.include
.add(resolve('src/assets/icons')) // svg图标的路径
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]' // 设置svg中symbol中id的值
})
}
}
前面也提到过了,使用SVG Sprite,需要有一个模板,比如/src/components/svgSprites/Icon.vue
组件:
<!-- /src/components/svgSprites/Icon.vue -->
<template>
<svg :width="width" :height="height" :style="{color: iconColor}">
<use xmlns:xlink="http://www.w3.org/1999/xlink" :xlink:href="iconId"></use>
</svg>
</template>
<script>
export default {
name: 'Icon',
props: {
iconName: {
type: String,
default: 'box'
},
width: {
type: [Number, String],
default: 32
},
height: {
type: [Number, String],
default: 32
},
iconColor: {
type: String,
default: 'currentColor'
}
},
computed: {
iconId() {
return `#icon-${this.iconName}`
}
}
}
</script>
虽然配置和模板都有了,但要使用还是需要人肉的引用.svg
文件。所以我们借助Webpack的一些功能,来自动帮我们引入。接下来在/src
目录下创建一个utils/svg-icons
目录,并在该目录下创建一个index.js
:
// src/utils/svg-icons/index.js
import Vue from 'vue'
import iconSpriteLoadComponent from '../../components/svgSprites/Icon.vue'
Vue.component('icon',iconSpriteLoadComponent)
const requireAll = requireContext => requireContext.keys().map(requireContext)
const req = require.context('../../assets/icons', false, /\.svg$/)
requireAll(req)
上面这段代码主要是用于注册Icon
组件和批量引入.svg
文件。有了这些之后,我们就可以像下面这样使用:
<icon icon-name="close"></icon>
<icon icon-name="love"></icon>
<icon icon-name="star" width="48" height="48"></icon>
将看到的效果如下:
通过Icon.vue
组件中的props
定义的属性,将相关的值传入进去。同样可以设置大小,但这里有一个小细节,如果你想像前面的示例通过iconColor
来给图标传递相应的颜色时会失效,比如:
<icon icon-name="location" width="24" height="24" icon-color="red"></icon>
<icon icon-name="collection" width="48" height="48" icon-color="lime"></icon>
颜色并没有重置过来:
那是因为,我们的.svg
文件中path
标签中的fill
有一个默认值:
如果要让iconColor
值生效,我们需要将.svg
中的fill
(和stroke
)属性的值设置为**currentColor
**。修改之后,你就能看到图标有相应的颜色了:
这里是通过人肉去修改每个
.svg
文件中fill
和stroke
属性,即将其值修改为currentColor
。不知道有没有相应的工具,能在合并成SVG Sprite时,自动将fill
和stroke
的值更换为currentColor
。找了一圈,没找到。如果您在这方面有相应的经验,欢迎分享出您的方案。
上面是在Vue Cli3环境中的使用,如果你是自己的构建系统,那么可以借助Webpack相关配置,达到相应的效果。只需要修改/build/webpack.base.conf.js
文件:
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
exclude: [resolve('src/assets/icons')], // 新增加
loader: 'url-loader',
options: [
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
]
}
// 另外增加
{
test: /\.svg$/,
loader: 'svg-sprite-loader',
include: [resolve('src/assets/icons')],
options: [
symbolId: 'icon-[name]'
]
}
其他地方可以不修改。
注册事项
虽然SVG图标在Web中的运用越来越广泛,但是在使用和设计的时候还是需要注意一些细节,比如.svg
文件的优化。大家都知道,用文本编辑器打开一个.svg
文件,将看到一大坨的代码,如果你不知道什么可删除,什么不可删除,那么可以借助SVGOMG来优化:
代码上的变化如下:
另外有一点也需要特别注意,那就是SVG 图示的绘制。虽然在很多平台上能直接下载到自己需要的SVG图标,但有时候为了更适合自己的项目,设计师会为自己提供一些更具个性化的SVG图标,这个时候,需要一套较为标准的绘制规则和流程。有关于这方面可以参考iconfont上的图标绘制规则和制作流程。
写在最后
有关于在Vue项目中使用SVG的方案和相关的组件库在Github上非常的多。大家可以选择适合自己的组件,如果你喜欢Font Awesome图标的话,那么@E0 大大的Vue-Awesome就是一个不错的选择。
最后再说一点,这篇文章主要是亲测了在Vue项目中怎么灵活或者更好的使用SVG图标。文章开头跟着Vue官网提供的方式,做了一些尝试,感觉较为简单,特别适合那种,项目中运用图标不多的场景,可以为每个图标创建一个独立的组件,不需要全局注册,也可以在任何需要的地方使用。如果图标较多时,管理起来有点蛋疼而以。文章第二部分,介绍如何自动化生成SVG Sprite,并且在相应的脚手架中运用。
可能文章中有不对或者不好的地方,如果你有这方面的经验,欢迎拍正和分享。文章中示例代码可以在Github中获取到。Sneaker Bar Detroit (SBD)