在开始了解如何在 Vue 项目中规范的使用 icon 前,我们需要先了解一些 icon 最基础的知识点- sprite 技术。
Sprite 技术
目前,SVG Sprite最佳实践是使用symbol元素。symbol元素是什么呢?单纯翻译的话,是“符号”的意思。然而这个释义并不符合这里的场景。不知大家有没有用过Flash,symbol实际上就类似于Flash中的“影片剪辑”、或者“元件”。因此,我个人觉得,symbol应该解释为“元件”最为恰当!那,symbol和SVG Sprite又有什么关系呢?我们可以把SVG元素看成一个舞台,而symbol则是舞台上一个一个组装好的元件,这这些一个一个的元件就是我们即将使用的一个一个SVG图标。
于是,对于一个集合了三个SVG图标的SVG元素的代码结构会是这样:
<svg>
<symbol>
<!-- 第1个图标路径形状之类代码 -->
</symbol>
<symbol>
<!-- 第2个图标路径形状之类代码 -->
</symbol>
<symbol>
<!-- 第3个图标路径形状之类代码 -->
</symbol>
</svg>
每一个symbol就是一个图标元件,但是只有上面的代码,是无法呈现任何东西的。
因为一个 symbol 元素本身是不呈现的,只有 symbol 元素的实例(一个引用了 symbol 的
symbol 就像一件衣服,没人穿
而 use 元素是 SVG 中非常强大,非常重要的一个元素,尤其是在 web 开发中:
- 可重复调用;
- 跨 SVG 调用;
1、可重复调用
开发中,你好不容易用了很多坐标值,绘制了一个图形,如果再弄一个相同的图形,你会怎么办?再复制一遍代码吗?学编程的都知道一个道理,重复调用的东西我们就要封装,而在 SVG 中我们不用封装,只需直接重复
<svg>
<symbol>
<g id="shape">
<rect x="0" y="0" width="50" height="50" />
<circle cx="0" cy="0" r="50" />
</g>javascript:;
</symbol>
<use xlink:href="#shape" x="50" y="50" />
<use xlink:href="#shape" x="200" y="50" />
</svg>
同样的 symbol ,只是调用的时候 x 轴距离稍微不一样,我们可直接用
<svg width="500" height="110">
<use xlink:href="#shape" x="50" y="50" />
</svg>
而这个跨SVG调用就是“SVG Sprite技术”的核心所在。
试想下,我们只要在页面某处载入一个充满Sprite(symbol)的SVG文件(或直接include SVG代码),于是,在页面的任何角落,只要想使用这个图标,只要简单这一点代码就可以了,图标尺寸CSS控制,里面只有一个仅有xlink:href属性的use元素,Done! 完成!也即是说,在HTML层面,图标使用的代码成本,跟传统的CSS Sprite或者流行的font-face几乎无异,代码简洁,而且很好维护。所有的SVG图标都在一个SVG源上。尺寸可任意拉伸,且颜色可控,真乃Web图标的未来之星。
如何在 Vue 项目中使用 Sprite 技术
上诉我们了解了 Sprite 技术的基本原理,无非就是引入 iconfont.js (包含已生成的所有 symbol,一个用 js 生成的 svg 代码)然后就可以直接 use 了。但是有个缺点,就是不够直观,毕竟没人能直接从代码中看出自己引入的 icon 是什么样的,也不知道所需的 icon 是对应哪个图标名,每次使用都要去查文档,而且增删改图标时,都得生成新的 js 去替换原来的 iconfont.js。
所以在 Vue 项目中,我们可以使用 svg-sprite-loader ,这是一个 webpack loader ,事实只要是使用 webpack 编译的项目都可以用这个插件,它可以将多个 SVG 图片打包成 svg-sprite。
首先,我们在 Vue 项目中安装它:
npm install svg-sprite-loader -D 或 yarn add svg-sprite-loader -D
然后创建一个文件夹:
在 src 目录下创建文件夹,主要是用来存放要使用的 svg 图片文件,例如 src/svg
配置 webpackConfig:
既然安装了 svg-sprite-loader ,要使用它还需进行 webpack 的配置才行
1、Vue CLI3.0及以上版本,我们主要是使用 vue.config.js 进行 webpack 配置:
module.exports = {
chainWebpack: config => {
// 清空默认svg规则
config.module
.rule('svg')
.uses.clear()
config.module //针对svg文件添加svg-sprite-loader规则
.rule('svg1')
.test(/\.svg$/)
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]'
})
.end()
}
}
或者
module.exports = {
chainWebpack: config => {
// svg rule loader
const svgRule = config.module.rule('svg') // 找到svg-loader
svgRule.uses.clear() // 清除已有的loader, 如果不这样做会添加在此loader之后
svgRule.exclude.add(/node_modules/) // 正则匹配排除node_modules目录
svgRule // 添加svg新的loader处理
.test(/\.svg$/)
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]',
})
// 修改images loader 添加svg处理
const imagesRule = config.module.rule('images')
imagesRule.exclude.add(resolve('src/assets/icons'))
config.module
.rule('images')
.test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
}
}
2、而在之前旧的脚手架搭建的 Vue 项目,如 超融合项目这些,我们是在 src/build/webpack.base.conf.js 中配置:
module: {
rules: [
{
test: /\.svg$/,
loader: 'svg-sprite-loader',
include: [resolve('src/svg')],
options: {
symbolId: 'icon-[name]'
}
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
exclude: [resolve('src/svg')], // 去除默认图片处理对指定 svg 的影响
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
}
]
}
自动导入
当我们将要用的图标放入上诉操作中创建的文件夹时,我们还需要用到 webpack 的 require.context 去对这些图标文件进行导入。
require.context(directory,useSubdirectories,regExp):
- directory:说明需要检索的目录
- useSubdirectories:是否检索子目录
- regExp: 匹配文件的正则表达式
require.context("./test", false, /.test.js$/); 这行代码就会去 test
文件夹(不包含子目录)下面的找所有文件名以 .test.js 结尾的文件能被 require 的文件。 更直白的说就是
我们可以通过正则匹配引入相应的文件模块。
在 main.js 中加入以下代码:
const requireAll = requireContext => requireContext.keys().map(requireContext)
const req = require.context('./svg', false, /\.svg$/)
requireAll(req)
之后我们直接对svg 文件夹中的图标进行增删改就行,什么都不用管,就会自动生成 svg symbol了。
这个时候我们可以在项目中直接使用图标了:
<svg><use xlink:href="#icon-name"/></svg>
但是我们可以将其封装下成标准的 Vue 组件:
<template>
<svg :class="className" aria-hidden="true">
<use :xlink:href="iconName" />
</svg>
</template>
<script>
export default {
name: 'SvgIcon',
props: {
iconClass: {
type: String,
required: true,
},
className: { // 自定义 svg 类名,后期可根据类名修改 svg 样式
type: String,
default: '',
},
},
computed: {
iconName() {
return `#icon-${this.iconClass}`; // 拼接成设置好的 id 名格式
}
},
};
</script>
<style lang="less" scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
这样我们就大功告成,可以在 Vue 项目中,随心所欲的直接使用 SVG 文件了。