还在用字体图标吗,试试svg图标吧(内附vuecli-svg-sprite-loader插件)

前言,关于字体图标

相信每位前端童鞋都对字体图标不陌生,毕竟网页必然少不了用大量的图标来装饰页面效果。在很早的时候,我们一般都是用img来当作图标来使用,由于大量地使用img会造成http请求过多的问题,所以这类使用方式也很快就被抛弃了,即使雪碧图也退出了历史的舞台(因为各种定位写法真的太麻烦了!🙃)后来字体图标开始流行起来,因为这种相当于直接加载一类字体,不会使用本地图片,还算蛮方便的。于是乎,像fontawesome这种字体图标库也开始热门起来,但是这种库是不支持定制的,所以,又诞生了iconmoon、阿里巴巴出门的iconfont可以支持用户定制化的应用,极大方便了我们的图标管理和开发。包括我目前的公司也是用iconfont来管理各个项目的图标的,虽然iconfont提供了好几种使用方式,但用svg图标之前,一直都是用font-class的方式来使用,下面就聊聊关于font-class的一些不足之处,以及为什么要开始使用svg图标了😊文章最后,会介绍一个可以在vue项目中快速使用svg图标的vuecli-svg-sprite-loader插件

关于font-class的一些问题

首先,这些问题都是我在项目中亲身经历过的,每个问题的背后都会有一些故事,下面就开始展开说说。

在项目初期,开发人员不是很多,所以用font-class使用还是挺舒服的,毕竟使用挺简单,一个<i class="iconfont xxx">就可以直接把图标弄出来了。但是随着项目业务开始变得复杂,子系统越来越多,开发人员也从4人升至15人。业务变多,页面增多,导致图标也会越来越多,这样的话,会导致iconfont.css经常要更新,因为我们是用gitlab进行协作开发,所以,多人同时修改同个文件的话,就会出现代码冲突的问题(这是第一个问题)。为了解决iconfont文件代码冲突这个问题,我们开发人员决定把更新图标的权限交给一个人来管理更新,那个人就是我😂,接下来,我就会收到每个子系统负责人的qq弹窗更新iconfont请求:

hi,麻烦更新一下图标文件,谢谢啦~

于是乎,我就开始了每天至少更新2次font-class文件(5个文件)的工作…下载、解压、替换…我暗暗吐槽,这玩意为啥不能增量更新啊😂so,不能增量更新,是第二个问题。

后来,我们发现,字体图标是不支持多色图标的,如果想要用多色图标还只能用svg或者img了,同样是图标,还不能用同一种方案来解决了,所以,不支持多色图标,是第三个问题。

我们要去找图标的话,只能在iconfont的页面去找,无法在本地进行全图标预览,当图标越来越多,如果想快速找到新增的图标,就比较麻烦了。所以,无法本地全图标预览,过于依赖iconfont预览页面是第四个问题。

总结下,font-class目前遇到的这几个问题:

  • iconfont.css文件多人更新文件,容易导致git代码冲突
  • 无法增量更新
  • 不支持多色图标
  • 无法做到本地全图标预览

寻求解决方案

在遇到了font-class这些问题后,我就开始寻找解决方案。后来发现iconfont还提供了另一种使用图标的方式,symbol引用,于是我就开始了一番尝试和体验,使用过来,觉得真的可以解决目前font-class这些问题了!🎉

文中所说,svg图标有以下优点:

  • 支持多色图标

  • 支持通过font-size、color来调整样式

  • 缩放不会失真

  • ie9+

接下来,就要思考如何在项目中使用svg图标了

如何使用svg图标

最简单的方法是直接在项目import引入iconfont生成的iconfont.js,再加入一些css代码:

1
2
3
4
5
6
.icon {
width: 1em; height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}

接着,挑选相应图标的名字

1
2
3
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-xxx"></use>
</svg>

PS: 这种写法,实际上就是symbol 和 svg sprite方式来使用svg的,关于这种技术的相关文章可以参考使用SVG symbols建立图标系统未来必热:SVG Sprites技术介绍

但是,这种暴力的用法虽然是可以实现加载svg,但是仍然无法做到增量更新,经过一番研究,增量更新是完全可以实现的,实现的背后,只需要使用webpack的svg-sprite-loader,下面就来讲讲如何通过这个插件做到图标增量更新。

svg-sprite-loader

svg-sprite-loader是webpack的一个loader,这个插件主要就是可以把多个svg图标都统一打包成sprite,类似如下效果:

这些图标实际上都是在某个文件夹下统一交给svg-sprite-loader来合成,然后append到document.body上。不知道,读到这里的你,能不能和增量更新联系到一起呢?其实就是在项目通过某个文件夹统一管理svg文件,然后通过这个loader进行打包成sprite,这样,就实现了增量更新,如果想要新增图标,只需要把新加的svg文件放到对应的文件夹下就可以了!就如下图所示:

新增的话,iconfont网站上支持一个或者多个图标下载的,还可以直接复制svg,都很方便

image-20200519092414738

如何配置svg-sprite-loader和在项目中使用

在webpack或者vuecli3+下的项目配置都很简单,官方readme提供了详细的教程,下面我拿vue项目的配置举例吧,在vue.config.js上加上这些配置信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module.exports = {
chainWebpack: config => {
// use svg
config.module
.rule("svg")
.exclude.add(resolve("src/svg")) // url-loader不处理
.end();
config.module
.rule("icon")
.test(/\.svg$/)
.include.add(resolve("src/svg"))
.end()
.use("svg-sprite-loader")
.loader("svg-sprite-loader")
.options({
symbolId: "icon-[name]"
})
.end();
}
}

webpack配置好了,再配合一键导入src/svg目录下的所有svg图标,就可以啦~

1
2
3
const requireAll = requireContext => requireContext.keys().map(requireContext);
const req = require.context(".", false, /\.svg$/);
requireAll(req);

那么,怎么使用呢?我们要使用这些图标,最简单的方法无非就是通过use标签来使用

1
<use :xlink:href="iconId" />

但是如果直接这么写,太麻烦了,直接封装个vue组件就完事啦

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<template>
<i :class="['svg-icon', `svg-icon-${name}`, className]" :style="svgStyle">
<svg fill="currentColor" aria-hidden="true" width="1em" height="1em">
<use :xlink:href="iconName" />
</svg>
</i>
</template>
<script>
export default {
name: "SvgIcon",
props: {
name: {
type: String,
required: true
},
className: {
type: String
},
color: {
type: String
},
size: {
type: Number
}
},
computed: {
iconName() {
return `#icon-${this.name}`;
},
svgClass() {
if (this.className) {
return `${this.className}`;
}
return "";
},
svgStyle() {
const { color, size } = this;
const style = {};
color && (style.color = color);
size && (style.fontSize = `${size}px`);
return style;
}
}
};
</script>
<style scoped>
.svg-icon {
vertical-align: -0.125em;
line-height: 0;
display: inline-block;
}
</style>

使用的话,轻轻松松,和font-class没啥太大的区别~感觉更方便了有木有😊

1
<SvgIcon name='图标id' size="32" color="orange"/>

一些注意点

可能在使用过程中,你会遇到svg单色图标设置color不生效的问题。关于svg图标如何设置颜色,可以参考张鑫旭大佬的文章SVG图标颜色文字般继承与填充,上面vue的组件写法是使用了currentColor的方式,svg会继承父元素的color的值,但是前提是path或者g属性不能有fill和fill-rule属性,因为这会使得currentColor无法生效,在iconfont下载的图标,一般都会带有fill属性,需要手动去删掉:

image-20200519093844204

这样,设置color就没问题了~

image-20200519094339799

image-20200519094353583

项目图标预览

上文还有个比较需要解决的问题,就是需要本地图标全预览。用了本地图标文件管理,写个预览页面也是so easy的,继续使用webpack-require-context方法,把本地图标名都拿到,直接加载就可以啦

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<script>
// 获取所有svg的名称
const icons = require
.context("../svg", false, /\.svg$/)
.keys()
.map(name => name.replace(/^\.\/([\w-]+)\.svg/, "$1"));
export default {
name: 'SvgViewer',
methods: {
async handleIconClick(iconName) {
await navigator.clipboard.writeText(`<SvgIcon name='${iconName}' />`);
alert(`${iconName}图标代码已复制到剪切板`);
}
},
render() {
const { SvgIcon } = this.$options.components;
return (
<div class="icon-view">
<p>点一点图标就能取代码</p>
{icons.map(iconName => (
<div class="icon" on-click={() => this.handleIconClick(iconName)}>
<SvgIcon name={iconName} />
<span class="icon-name">{iconName}</span>
</div>
))}
</div>
);
},
}
</script>

配合vue render jsx写法,简直不要太舒服

看看效果:

image-20200518214323810

这样,无法预览本地图标的问题也得到解决了!点下图标,代码还能自动copy到剪切板,提高开发效率🚀!

做成vuecli插件,一键快速集成

后来我把这个方案在公司内部进行推广,很多同学都对这个方案感兴趣,想在项目中进行尝试。考虑到要集成到项目中的话,手动集成的方法还是比较麻烦的(包括自己要集成的话),于是我决定做个vuecli插件出来(因为公司百分之90项目都是vue),这样可以方便大家快速集成。 开发vuecli插件蛮简单的,官方文档写得很清晰,很快就做出来了:

vuecli-svg-sprite-loader

​ 使用的话,非常简单

vue add svg-sprite-loader

更多信息,欢迎在github上查阅,如果使用上需要有哪些地方需要改进的,或者有什么bug的,欢迎在issue上提出,交流!如果这个插件帮到了你,我非常开心!

文章作者: Rao Jinwei
文章链接: http://shooterblog.site/2020/05/18/还在用字体图标吗,试试svg图标吧(内附vuecli-svg-sprite-loader插件)/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明来自 Rao Jinwei's Blog