我们经常会用到各种各样的小图标,也有各种奇技淫巧可以用来引入图标,这里将简单介绍几种我们常用的图标引入方式,里面有古代的,也有现代的。
雪碧图
雪碧图可以说是一种比较古老的引入图片的方式了,这里只是介绍一下他的原理。
在当初没有比较方便的图标管理方式的时候,我们可能需要使用 CSS 或者 JS 来一个一个引入诸如 png 格式的图标,同时如果一个图标多次引用,可能需要多次请求,导致性能不太好。
于是就出现了一种解决方案,他将所有的小图标放到一个图片里面,通过 background-image
引入图片,通过 background-position
来控制是显示的哪一个图标,这样实际上所有的图标都是使用的同一张图片了。
但是有个很大的问题是管理和维护起来比较麻烦,因为你不知道引入的到底是谁,后续增加图标难度也比较大,因此现在用的人已经比较少了。
iconfont
iconfont 是一个不错的图标管理方案,有多种使用场景:
- 我们想要找一个图标:
- 输入关键词
- 加入购物车
- 在购物车中结算(加入项目)
- 在项目中查看,iconfont 提供了 Unicode、Font class、Symbol 三种不同的引入方案,并且附有教程
- 我想管理我的图标:
- 上传 SVG
- 像之前那样用 :)
好了,因为 iconfont 太方便了,所以话题结束。
…………
当然不是!如果没有 iconfont,我们该如何引入图标呢?或者说 iconfont 的 symbol 是怎么引入的呢?且看下文。
SVG
SVG 是什么?
看来我们还得从 SVG 说起。
可缩放矢量图形(Scalable Vector Graphics,SVG),是一种用于描述基于二维的矢量图形的,基于 XML 的标记语言。本质上,SVG 相对于图像,就好比 HTML 相对于文本。
这样看来,SVG 实际上是比较复杂复杂的,现在我们只讨论其中一部分对我们有用的东西,先来看看一个 SVG 图标的代码:
<svg t="1581476689547" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2005" width="200" height="200">
<path d="M466.88 908.96L113.824 563.296a270.08 270.08 0 0 1 0-387.392c108.8-106.56 284.896-106.56 393.696 0 1.504 1.472 2.976 2.944 4.448 4.48 1.472-1.536 2.944-3.008 4.448-4.48 108.8-106.56 284.896-106.56 393.696 0a269.952 269.952 0 0 1 34.016 347.072l-387.392 385.6a64 64 0 0 1-89.92 0.384z" p-id="2006"/>
</svg>
一个 <svg>
标签,包裹着一个 <path>
标签,其中 <path>
就定义了图片的形状。
我们可以像普通图片一样使用 svg
:
<img src="./src/heart.svg">
SVG 雪碧图
使用 <symbol>
,我们可以轻松构建 SVG 雪碧图,就像这样:
<svg>
<symbol id="icon1" viewBox="0 0 1024 1024">
<path/>
</symbol>
<symbol id="icon2" viewBox="0 0 1024 1024">
<path/>
</symbol>
<symbol id="icon3" viewBox="0 0 1024 1024">
<path/>
</symbol>
</svg>
每一个 <symbol>
里面就装了一个图标。现在就不能像之前那样直接用 <img>
标签来使用啦,我们可以先在 HTML 中某处引入整个 SVG 图片,再通过 <use>
来使用。
<svg class="icon">
<use xlink:href="#icon1"/>
</svg>
这跟之前提到的雪碧图有点像,所有的图标其实都在一个 SVG 源上,主要的优点在于:
- 跟雪碧图一样,不需要多次加载
- 代码简洁方便维护
- 尺寸可以任意拉伸
- 可以通过
fill
或者父元素的color
属性很方便地调整颜色
最大的缺点是兼容性,IE 9 以下没办法很好地使用,但现在这当然不是问题啦。
引入一个 icon
但是我们总不可能每次都自己搞一个 SVG,然后每来一个新图标就装到 symbol
里面吧,然后对于向 Vue 这样的框架我们由该怎么引入呢?或者说我们如何在 JS 而不是 HTML 中引入呢?
我们可以试一下直接在 JS 中 import
一个 SVG,发现他将会得到一个路径,而不是我们想要的图片,也没办法直接在 template 或者 JSX 里面用。
为了解决这个问题,我们可以使用 svg-sprite-loader。
如果是使用的 webpack,我们需要按照文档里面提供的方法进行配置 webpack.config.js
const SpriteLoaderPlugin = require('svg-sprite-loader/plugin')
module.exports = {
module: {
rules: [
{
test: /\.svg$/,
exclude: path.resolve(__dirname, 'src/assets/icons')
},
{
test: /\.svg$/,
loader: 'svg-sprite-loader',
include: path.resolve(__dirname, 'src/assets/icons'),
options: { extract: false }
}
]
},
plugins: [
new SpriteLoaderPlugin({ plainSprite: true })
]
}
如果是使用的 vue-cli,我们需要这样配置 vue.config.js
:
module.exports = {
chainWebpack: config => {
config.module
.rule('svg-sprite')
.test(/\.svg$/)
.include.add(path.resolve(__dirname, 'src/assets/icons')).end()
.use('svg-sprite-loader').loader('svg-sprite-loader').options({ extract: false }).end()
config.plugin('svg-sprite')
.use(require('svg-sprite-loader/plugin'), [{ plainSprite: true }])
config.module
.rule('svg')
.exclude.add(path.resolve(__dirname, 'src/assets/icons')).end()
}
}
弄好 svg-sprite-loader
之后再在 JS 中 import
SVG,就会发现 HTML 中多出来了一个 <svg>
标签,里面是一些 <symbol>
,这意味着我们就可以通过 <use>
来使用他们啦!
引入一整个 icon 目录
我们可以通过 webpack 的 require.context 来引入一整个文件夹,他接受三个参数:第一个是文件夹,第二个是是否使用子文件,第三个是匹配文件的正则。
let importAll = (requireContext) => requireContext.keys().forEach(requireContext)
try {
importAll(require.context('./assets/icons', true, /\.svg$/))
} catch (error) {
console.log(error)
}
封装一个 Icon 组件
这里使用 Vue 做代码演示,先写他的 template,非常简单,就是一个 <svg>
里面有一个 <use>
,:xlink:href
的值就是对应 <symbol>
的 id
。
<template>
<svg class="icon" @click="$emit('click',$event)">
<use :xlink:href="'#'+name"/>
</svg>
</template>
然后是中间的 JS,接受一个 Prop 即可:
let importAll = (requireContext) => requireContext.keys().forEach(requireContext);
try {
importAll(require.context('../assets/icons', true, /\.svg$/));
} catch (error) {
console.log(error);
}
export default {
name: 'Icon',
props: ['name']
};
最后是从 iconfont 里面抄的 icon 的基本样式:
.icon {
width: 1em; height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
这样以后我们就可以通过 Icon 组件来使用图标了,如果需要新加图标,下好之后就放在图标文件夹里面就可以了。
<Icon name="heart"/>
小坑
有时候咱们下载下来的 SVG 里面是自带了颜色(fill
属性)的,这样我们之后就没办法修改颜色了,这个时候可以使用 svgo-loader 来统一去除颜色。
在 webpack.config.js
中:
const SpriteLoaderPlugin = require('svg-sprite-loader/plugin')
module.exports = {
module: {
rules: [
{
test: /\.svg$/,
exclude: path.resolve(__dirname, 'src/assets/icons')
},
{
test: /\.svg$/,
use: [
{ loader: 'svg-sprite-loader', options: { extract: false } },
{
loader: 'svgo-loader',
options: { plugins:[{removeAttrs: {attrs: 'fill'}}] }
}
]
include: path.resolve(__dirname, 'src/assets/icons'),
}
]
},
plugins: [
new SpriteLoaderPlugin({ plainSprite: true })
]
}
在 vue.config.js
中:
module.exports = {
chainWebpack: config => {
config.module
.rule('svg-sprite')
.test(/\.svg$/)
.include.add(path.resolve(__dirname, 'src/assets/icons')).end()
.use('svg-sprite-loader').loader('svg-sprite-loader').options({ extract: false }).end()
.use('svgo-loader').loader('svgo-loader')
.tap(options => ({ ...options, plugins: [{ removeAttrs: {attrs: 'fill'} }] }))
config.plugin('svg-sprite')
.use(require('svg-sprite-loader/plugin'), [{ plainSprite: true }])
config.module
.rule('svg')
.exclude.add(path.resolve(__dirname, 'src/assets/icons')).end()
}
}