在vue项目中,我们除了使用组件库(ant-design-vue element-ui)的icon以外,还有我们自己自定义的图标,对于自定义图标的话,我们暂时还缺少显示的方式。所以说我们需要一个自定义的组件,来显示我们自定义的 svg 图标。
对于这个组件的话,它就需要拥有两种能力:
- 显示外部
svg图标,比如: 'res.lgdsunday.club/user.svg' - 显示项目内的
svg图标,即从iconfont等网站下载的svg图标文件
1. 首先导入所有的 svg 图标
现在我们从iconfont网站上下载了很多svg的图标文件,我们怎么使用它呢?
import UserIcon form '@src/icon/user.svg'
<svg class="svg-icon" aria-hidden="true">
<use :xlink:href=" UserIcon" />
</svg>
如果整个项目有100个图标,我们就需要import 100 多次,非常麻烦,那有没有什么办法一次性全部把svg文件全部引入,然后导入到入口文件main.js里面呢?
答案是require.context。通过执行require.context函数获取一个特定的上下文,主要用来实现自动化导入模块,在前端工程中,如果遇到从一个文件夹引入很多模块的情况可以使用这个api,它会遍历文件夹中的指定文件,然后自动导入,使得不需要每次显式的调用import导入模块。
require.context函数接受三个参数:
- directory {String} -读取文件的路径
- useSubdirectories {Boolean} -是否遍历文件的子目录
- regExp {RegExp} -匹配文件的正则
const svgRequire = require.context('./svg', false, /\.svg$/)
require.context函数执行后返回的是一个函数,这个函数就是一个require函数,作用跟import一样。并且这个函数有3个属性:
- resolve {Function}: 接受一个参数request,request为test文件夹下面匹配文件的相对路径,返回这个匹配文件相对于整个工程的相对路径
- keys {Function}: 返回匹配成功模块的名字组成的数组
- id {String}: 执行环境的id
这三个都是作为函数的属性(注意是作为函数的属性, 函数也是对象,有对应的属性)
svgRequire.keys().forEach((svgIcon) => svgRequire(svgIcon))
2、SvgIcon组件
<template>
<div
v-if="isExternal"
:style="styleExternalIcon"
class="svg-external-icon svg-icon"
:class="className"
/>
<svg v-else class="svg-icon" :class="className" aria-hidden="true">
<use :xlink:href="iconName" />
</svg>
</template>
// 判断是否是外部svg,如:https://res.lgdsunday.club/user.svg
const isExternal = computed(() => external(props.icon))
const styleExternalIcon = computed(() => ({
mask: `url(${props.icon}) no-repeat 50% 50%`,
'-webkit-mask': `url(${props.icon}) no-repeat 50% 50%`
}))
// 项目内的svg图标文件
const iconName = computed(() => `#icon-${props.icon}`)
3. 把组件注册为全局组件,并在main.js引入
export default (app) => {
app.component('svg-icon', SvgIcon)
}
// main.js 导入 svgIcon
import installIcons from '@/icons'
installIcons(app)
4. 使用 svg-sprite-loader 处理 svg 图标
通过上面的处理之后,打开浏览器,我们发现 图标依然无法展示! 这又是因为什么原因呢?
这是因为我们没有对应的loader来处理后缀为.svg的图标文件,其实也不是没有,通过npm 润inspect把webpack的配置调出来:
"scripts": {
"inspect": "vue-cli-service inspect --mode development >> webpack.config.development.js"
}
发现处理svg文件的是file-loader,但是这个loader不对svg文件不做任何操作,只是把资源移动到打包之后的文件中,并修改对应的链接。
/* config.module.rule('svg') */
{
test: /\.(svg)(\?.*)?$/,
exclude: [
'/Users/pengchangjun/Documents/admin-template/pcj-admin/src/icons'
],
use: [
/* config.module.rule('svg').use('file-loader') */
{
loader: '/Users/pengchangjun/Documents/admin-template/pcj-admin/node_modules/file-loader/dist/cjs.js',
options: {
name: 'img/[name].[hash:8].[ext]'
}
}
]
},
这种使用方式是利用img或者css来展示:
<img src="https://res.lgdsunday.club/user.svg" alt="" />
但是我们对svg的使用方式:
<svg v-else class="svg-icon" :class="className" aria-hidden="true">
<use :xlink:href="iconName" />
</svg>
所以,我们需要特殊的一个loader来处理svg图标,这个loader是svg-sprite-loader,它 是 webpack 中专门用来处理 svg 图标的一个 loader。
- 下载: npm i --save-dev svg-sprite-loader@6.0.9
- 创建
vue.config.js文件,新增如下配置:
chainWebpack(config) {
// 设置 svg-sprite-loader
// 首先把原来svg应用的loader(file-loader)先排除
config.module.rule('svg').exclude.add(resolve('src/icons')).end()
// 专门增加一个icons的规则
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]'
})
.end()
}
处理完以上配置之后,重新启动项目,图标即可显示!
我们打开控制台,可以看到body下面有一个svg标签,里面就是我们导入的所有svg的icon。