# 前言
前端攻城狮们在做前后台项目的时候经常会用到很多icon
图标,刚开始还好,用的图片不多,没什么感觉,但随着项目迭代,体积与功能不断增大,修改和添加图标变得比较麻烦,而且总觉的不够优雅。
# 几种图标使用办法
标签
<image>
想必大家多多少少都用过。简单,让设计师提供一张图片一丢即可。但其缺点尤为突出。image
作为网页依赖资源存在,在页面载入时需要去服务器获取下载,这不仅占用了网络资源,更严重的是拖慢了页面的展示。雪碧图
sprite
前几年雪碧图几乎成为前端面试中必问的题,它的出现就是为了解决<image>
占用过多下载线程。它就是将多个图片合成一个图片,然后利用 css 的background-position
定位显示不同的 icon 图标。但使用过的同学都有感受,维护起来比较困难。每新增一个图标,都需要改动原图片,还可能不小心出错影响到前面定位好的图片;不仅如此,一修改雪碧图,之前的缓存就失效了。Iconfont
相信这是现阶段绝大多数开发者使用的办法。在此之前,Font Awesome也是主流用法之一,笔者用的较少。现在的UI库就拿 element-UI 来说,本身提供有少量高可用图标,但依旧不能满足日益迭代的特别是设计师的刁钻要求。阿里爸爸开发的iconfont
开源图库提供了海量的图标,支持自定义图标库下载,单图标定制颜色,下载不同类型(svg,png,ai),使用上也支持(unicode,font-class,symbol )三种方式
有一点不方便就是,你在编辑自定义图库时需要预知网站将用到些什么图标,这太难了,往往后续需要使用添加图标库或者修改已有库的方式来扩大图标,好在一次性可以增加多个图标,比雪碧图改善了太多SVG类雪碧图
SVG-Sprite
SVG-Sprite
,不仅有着雪碧图的优势,又避免了改动和维护困难的问题,而且具有svg矢量图标的优点,集多优势于一身的它,必将成为网站图标的未来之星。
# SVG Sprite 技术实现
SVG Sprite 与普通 Sprite 具有类似思想,即将图标图形整合在一起,利用CSS实现精准定位显示特定图标。
1.【基础】SVG Sprite与symbol元素
目前,SVG Sprite的最佳实践是symbol元素。单纯翻译即“符号”,但在这不是这意思。它类似于Flash中的“影片剪辑”,或者“元件”,理解为元件,模块
更加贴切。
一个干净的 SVG Sprite 由 <svg>
, <symbol>
标签组合而成,类似如下
<svg>
<symbol id="icon-name1">...</symbol> // 第一个图标路径形状
<symbol id="icon-name2">...</symbol> // 第二个图标路径形状
<symbol id="icon-name3">...</symbol> // 第三个图标路径形状
</svg>
如上<svg>
容器就类似于雪碧图的那张大图,而每一个<symbol>
则是每一个被实际定位显示的图标,SVG Sprite 使用id
作为精准定位依据提取目标资源,最终复制到SVG use
位置,强烈建议id
语义化定义。不理解的可以接着往下看。
2.【基础】SVG 中的 use元素
use
元素是SVG中非常重要的一个元素,它支持在SVG内提取目标节点,并在别的地方复制它们。其效果等同于这些节点被深克隆到一个不可见的DOM中,然后粘贴到use
使用的位置。它有两个特征:
(1)可以重复调用
SVG是代码型矢量图片,其占用代码行较多。一个图片多处写会严重影响代码可阅读性。use
完美拯救了这个问题,通过xlink:href
定位需要的元件,使用如下,#
号表示取id
<use xlink:href="#icon-name1" x="50" y="50" /> // x, y 定位显示位置
(2)支持跨SVG调用
跨SVG调用特征是 SVG-Sprite 技术使用核心所在
简单理解就是,SVG内部可以use
别的SVG内定义的symbol。假设有 1 中的SVG Sprite图,则使用如下
<svg class="scgClass">
<use xlink:href="#icon-name1" />
</svg>
这里我们还设计了一个svgClass
,在Vue项目中可以通过prop
进来,通过这个类来控制SVG显示的样式如大小,颜色等。
从这个案例中获取你已经感受到,我们会封装一个<svg-icon>
的组件,外部引用这个组件,传入 svgClass
及图标的icon-name
控制展示及样式,达到HTML层面优雅的使用效果
3.【基础】SVG 的 aria-hidden 和替代文本
类似<img alt="提示文案">
中的alt
属性,SVG提供有替代文本以便屏幕阅读器遇到SVG图标时读取的文案。SVG一般都需要设置aria-hidden="true"
以便阅读器跳过阅读
<svg aria-hidden="true">
<use xlink:href="#icon-name1" />
</svg>
当遇到 a
标签或者button
的内容只有是图标时,可以在a
或button
标签上设置阅读替代文本属性aria-label
,而SVG依然跳过,保证用户体验
<a href="/news/" arialabel="Latest News">
<svg aria-hidden="true"><use xlink:href="#icon-name1"></svg>
</a>
4. 【实战】webpack配置svg-sprite-loader
- 在
package.json
中添加插件
npm install svg-sprite-loader --save-dev
- 配置webpack,修改
vue.config.js
。
注意默认的SVG使用的是url-loader
来解析的,我们只针对icon部分使用svg-sprite-loader
处理,因此需要用到exculde
和include
命令区分。
// 设置svg图片-图标处理方式
config.module
.rule('svg')
.exclude.add(resolve('src/icons')) // 除了src/icons下的svg都是用url-loader来处理
.end()
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/icons')) // svg-sprite-loader只处理src/icons下的svg
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]'
})
.end()
有了这个区分,就要求我们规范的管理SVG图标,将其统一放置在src/icons
目录下。
【注】为什么不是src/icons/svg
?因为在下一步会去生成SVG Sprite,而他不在该目录里。即src/icons/svg
只是源svg图标,SVG Sprite在src/icons
里,所以用src/icons
。只要保证正常SVG图片不往这里放即可。
5. 【实战】自动导入生成SVG Sprite require.context
先理解一下,既要有维护容易的增添单个图标,又要有使用方便的SVG Sprite。这两者本身相悖,为满足这个功能,我们需要一个从图标组里生成SVG Sprite的能力,好在webpack提供了require.context
自动导入功能,满足了该需求。我们在与@/icons/svg
同级目录下编写该逻辑
先入为主,SvgIcon.svg
是下一步要实现的组件封装,因为必然有许多地方需要用到图标,所以对SvgIcon组件进行全局注册
// /icons/index.js
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'
// 全局注册SvgIcon组件
Vue.component('svg-icon', SvgIcon)
const req = require.context('./svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys().map(requireContext)
requireAll(req)
require.context
接受三个参数,1-源svg文件夹,2-是否检索下级目录,3-匹配资源正则表达式。
以上代码片段的意思是:在svg文件夹下,不检索其子目录,寻找后缀名为.svg的能被require所有资源图标。需要注意的是生成的SVG Sprite元件的id
名,会加上icon-
前缀。其最终会生成一个类似如下的SVG Sprite
6. 【实战】上手写<svg-icon>
组件
接下来实现4中的SvgIcon组件,用于在HTML中优雅的编写引入的图标。我们在该组件中还区分了图标是否是外联SVG图标,iconClass
表示图标的语义化名称(一般为svg图片名字),className
表示图标个性化样式。因为生成SVG Sprite时symbol的id
被加上了icon-
,为了不影响引用,我们需要在组件实现时把类名的前缀加上
// components/svg-icon.vue
<template>
<div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
<svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
<use :xlink:href="iconName" />
</svg>
</template>
<script>
export default {
name: 'SvgIcon',
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String,
default: ''
}
},
computed: {
isExternal() {
return this.isExternal(this.iconClass)
},
iconName() {
return `#icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
} else {
return 'svg-icon'
}
},
styleExternalIcon() {
return {
mask: `url(${this.iconClass}) no-repeat 50% 50%`,
'-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
}
}
},
methods: {
// 判断是否是外部图标
isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path)
}
}
}
</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>
7. 页面使用SVG Sprite
有了生成SVG Sprite的能力,也有了优雅使用 SVG Icon 组件,接下来就是怎么用了。
如果你有设计师提供SVG图标最好,有许多设计师使用sketch来工作,可以很方便的导出SVG。如果没有设计师或不方便实时沟通,可以借用阿里爸爸的IconFont,它支持下载SVG格式的图片,下载下来放到src/icons/svg
文件加下即可,require.context
会自动更新成SVG Sprite,开发者只需要管怎么引用即可。
<svg-icon icon-class="eye" class-name="eyeStyle" />
本例中,eye
表示SVG图标的名称(eye.svg),eyeStyle
是该svg图标的自定义样式。如果不需要自定义样式,则代码段更短。
# 拓展
生成的SVG Sprite
会不会太大?
首先我们来看一下从阿里icofont
网站上导出的svg长什么样
对比于设计师可能给我们的svg图标,这个已经很精简了,但其实还有很多无用的信息,造成了冗余。
好在svg-sprite-loader
考虑到了这点,它目前只会获取svg中path
的内容,而其他的信息一概不要,因此生成的sprite
也都是精华的集合,且SVG本身就是代码型图片,无需太担心资源大小问题。当然也要适合的控制你的svg图标,没用的就不要加进来了。