Preface {#preface}
在 Vue 前端项目中不可避免的要使用 icon svg 图标组件,对于这个组件它需要两种能力:
- 显示外部 svg 图标。
- 显示项目内的 svg 图标。
基于以上概念,我们先来实现显示外部 svg 图标。
显示外部 svg 图标 {#显示外部-svg-图标}
- 首先在项目中创建
src/components-global/svg-icon/index.vue
,编写如下代码:
<template>
<div
v-if="isExternal"
:style="styleExternalIcon"
class="svg-external-icon svg-icon"
:class="className"
/>
</template>
<script setup>
import { defineProps, defineOptions, computed } from 'vue'
defineOptions({
name: 'SvgIcon'
})
const props = defineProps({
// icon 图标
icon: {
type: String,
required: true
},
// 图标类名
className: {
type: String,
default: ''
},
// 同时设置宽度和高度
size: {
type: String
},
// 宽度
width: {
type: String,
default: '1em'
},
// 高度
height: {
type: String,
default: '1em'
}
})
/**
* 判断是否为外部图标
*/
const isExternal = computed(() => {
return /^(https?:|http?|mailto:|tel:)/.test(props.icon)
})
/**
* 外部图标样式
*/
const styleExternalIcon = computed(() => ({
/*
在 CSS 中,mask 属性用于定义图像蒙版或剪切蒙版。
它允许您通过指定一个图像或 SVG 元素作为另一个元素的蒙版来创建复杂的视觉效果。
mask 属性通过根据蒙版的透明度值隐藏元素的部分来实现。
*/
mask: `url(${props.icon}) no-repeat 50% 50%`,
'-webkit-mask': `url(${props.icon}) no-repeat 50% 50%`,
// 设置宽高
width: props.size ?? props.width,
height: props.size ?? props.height
}))
</script>
<style scoped>
.svg-icon {
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
.svg-external-icon {
background-color: currentColor;
mask-size: cover !important;
display: inline-block;
}
</style>
- 编写完成之后,我们在 App.vue 使用:
http://res.lgdsunday.club/user.svg
这个链接来测试外部 svg 图标是否引用成功。
<template>
外部图标测试:<svg-icon icon="http://res.lgdsunday.club/user.svg" />
</template>
<script setup>
import SvgIcon from '@/components-global/svg-icon'
</script>
<style lang="scss"></style>
显示效果如下:
显示内部 svg 图标 {#显示内部-svg-图标}
- 首先需要准备一些 svg 图标文件,可以从iconfont-阿里巴巴矢量图标库中下载,下载完成之后放到项目中的
src/assets/icons/svg
目录下。
- 创建
src/assets/icons/index.js
。
import SvgIcon from '@/components-global/svg-icon'
/*
https://webpack.docschina.org/guides/dependency-management/#requirecontext
require.context 是一个在 Webpack 中用于创建模块上下文的函数。
它的主要作用是实现动态地导入模块,特别是用于导入一组符合特定规则的模块。
通常,它用于在前端项目中处理导入大量模块或文件的情况,例如导入所有的图片、样式文件或特定目录下的组件。
require.context 接受三个参数:
1. 要搜索的目录:一个字符串,表示要搜索的目录路径。
2. 是否搜索其子目录:一个布尔值,表示是否要搜索指定目录的子目录。
3. 匹配文件的正则表达式:一个正则表达式,用于匹配文件名。
使用 svgContext.keys() 获取所有匹配的文件路径,然后通过 svgContext(svgIcon) 来动态导入每个 SVG 文件
*/
const svgContext = require.context('./svg', false, /\.svg$/)
svgContext.keys().forEach(svgIcon => svgContext(svgIcon))
export default Vue => {
// 这里为什么将变量名设置为 Vue 呢?请参考:https://juejin.cn/post/7114133835339530276
Vue.component('svg-icon', SvgIcon)
}
- 在
main.js
中引入该文件。
...
// 导入 SvgIcon
import installIcons from '@/assets/icons'
...
// 安装 svg 图标和注册 SvgIcon 组件
installIcons(app)
...
- 使用
svg-sprite-loader
处理 svg 图标
svg-sprite-loader 是 webpack
中专门用来处理 svg
图标的一个 loader
。
下载该 loader
,执行:npm i --save-dev svg-sprite-loader
。
创建 vue.config.js
文件,新增如下配置:
const { defineConfig } = require('@vue/cli-service')
const path = require('path')
/**
* __dirname 是当前执行脚本文件的目录,
* 因此 resolve 函数会将相对路径与该目录拼接,返回一个绝对路径。
*/
const resolve = (dir) => {
return path.join(__dirname, dir)
}
// https://cli.vuejs.org/zh/guide/webpack.html
module.exports = defineConfig({
chainWebpack (config) {
/*
配置 svg-sprite-loader 来处理 SVG 图标,将多个独立的SVG图标文件打包成一个单独的SVG精灵图(SVG sprite)
1. 设置排除规则(exclude)和包含规则(include):
1.1 config.module.rule('svg') 定义了一个规则,用于处理所有以 .svg 结尾的文件,
但它通过 .exclude.add(resolve('src/assets/icons')) 来排除了 src/assets/icons 目录下的 SVG 文件。
这意味着 src/icons 目录下的 SVG 文件不会被该规则处理,而是由下面的规则单独处理。
1.2 config.module.rule('icons') 定义了一个规则,用于处理 src/icons 目录下的 SVG 文件,通过 .include.add(resolve('src/assets/icons')) 来指定只处理该目录下的文件。
2. 使用 svg-sprite-loader:
2.1 通过 .use('svg-sprite-loader') 来配置使用 svg-sprite-loader 作为处理 SVG 文件的 loader。
2.2 通过 .loader('svg-sprite-loader') 指定加载器的名称为 svg-sprite-loader。
2.3 使用 .options({ symbolId: 'icon-[name]' }) 来配置 svg-sprite-loader 的选项,其中 symbolId 用于定义每个 SVG 符号的标识,通常在 SVG 精灵图中使用。
*/
config.module
.rule('svg')
.exclude.add(resolve('src/assets/icons'))
.end()
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/assets/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]'
})
.end()
}
})
- 编写完成之后,我们在 App.vue 使用第一步导入的
头像 男孩.svg
文件进行测试。
<template>
内部图标测试:<svg-icon icon="头像 男孩" size="28px" />
</template>
<style lang="scss"></style>
显示效果如下:
至此一个可以引用外部和内部 svg 图标的 SvgIcon
组件封装完毕。