Vue3中使用各类字体图标的正确姿势:本地SVG、Iconfont、FontAwesome、ElementPlus(Icon篇)
前言
最近,在项目的开发中,我们规划了一个 Icon
组件,我们希望通过这个组件,能直接同时使用多种图标库的图标(一种语法,实现无限的图标扩展和高度兼容性)并且,实现该 Icon
之后,理应还有一个图标选择器
,可以加载出不同图标库的所有可用图标,方便直接选择使用,文末附组件完整代码。
由于项目是基于
Vite
的,所以本文会出现少量只在Vite
下才能运行的代码(vite专有api等)。
我们的Icon
和Icon选择器
组件,实现了以下常用图标库的支持,如果还有其它你喜欢的图标库,可以参考我们的实现方式,自行加上即可。
- 本地SVG图标:直接将svg文件放入指定的文件夹内,实现自动加载该文件夹所有的svg,并利用
Icon
组件直接使用,无需手动import
。 - ElementPlus的icon,首先使用官方提供的方法全局注册,然后和
Icon
组件整合,实现语法的兼容性。 - Iconfont(阿里巴巴矢量图标库),实现了自动载入
Font clas
(css链接,载入后即可通过class来使用对应的字体图标),实现Icon
组件的语法兼容性,然后自动解析出Font class内的所有图标名称,以供图标选择器使用。 - FontAwesome,这是一款很常用的图标库,包含了675个图标,
Icon
组件实现了自动加载,语法兼容;并且自动解析所有图标名称,以供图标选择器使用。
使用四种图标的语法
<!-- 本地图标 -->
<Icon name="local-图标文件名" size="18px" color="#000000" />
<!-- Element Plus图标 -->
<Icon name="el-icon-图标名" size="18px" color="#000000" />
<!-- FontAwesome图标 -->
<Icon name="fa fa-图标名" size="18px" color="#000000" />
<!-- Iconfont图标 -->
<Icon name="iconfont 图标名" size="18px" color="#000000" />
具体实现
目录结构
BuildAdmin
├─src
│ │ App.vue
│ │ main.ts
| | vite.config.ts
│ ├─assets
│ │ └─icons 存放本地SVG文件的文件夹
│ ├─components
│ │ ├─icon
│ │ ├─svg
│ │ │ ├─index.ts 加载本地SVG文件的实现
│ │ │ ├─index.vue svg显示组件的实现
│ │ ├─index.vue Icon 组件的实现
│ │ └─selector.vue 图标选择器组件的的实现
| ├─utils
│ │ └─common.ts公共辅助函数库
本地SVG的实现
我们准备了一个目录/src/assets/icons
,将svg文件放入其中,实现自动加载全部文件(不依赖第三方包),并直接以:<Icon name="local-图标文件名" />
的语法使用图标。
第一步:SVG文件读取准备
文件:/src/components/icon/svg/index.ts
// 从fs库导入读取文件和读取文件夹的函数
// fs库是node自带的,无需通过npm进行install
import { readFileSync, readdirSync } from 'fs'
// 定义一个变量以保存所有的icon文件名称
let iconNames: string[] = []
众所周知,svg文件,是以XML的语法定义的矢量图片文件,我们需要对svg的XML内容进行一些特殊处理,比如清理掉width、height
属性等,我们定义了一些正则表达式,读取到svg文件内容之后,将这些正则匹配结果进行处理:
const svgTitle = /<svg([^>+].*?)>/
const clearHeightWidth = /(width|height)="([^>+].*?)"/g
const hasViewBox = /(viewBox="[^>+].*?")/g
const clearReturn = /(\r)|(\n)/g
const clearFill = /(fill="[^>+].*?")/g
接下来,我们首先实现一个查找SVG文件的函数(递归),将svg文件内容构建为symbol
元素
function findSvgFile(dir: string, perfix: string = 'local'): string[] {
const svgRes = [] // 一个目录下所有的svg文件资源
const dirents = readdirSync(dir, {
withFileTypes: true,
})
for (const dirent of dirents) {
// 替换掉.svg文件后缀,然后存入预设的图标名称数组内
iconNames.push(`${perfix}-${dirent.name.replace('.svg', '')}`)
if (dirent.isDirectory()) {
svgRes.push(...findSvgFile(dir + dirent.name + '/'))
} else {
// 读取svg文件内容,并对内容进行处理,组装为一个 symbol 元素,该元素的id属性是文件名称
const svg = readFileSync(dir + dirent.name)
.toString()
.replace(clearReturn, '')
.replace(clearFill, 'fill=""')
.replace(svgTitle, ($1, $2) => {
let width = 0
let height = 0
let content = $2.replace(clearHeightWidth, (s1: string, s2: string, s3: number) => {
if (s2 === 'width') {
width = s3
} else if (s2 === 'height') {
height = s3
}
return ''
})
if (!hasViewBox.test($2)) {
content += `viewBox="0 0 ${width} ${height}"`
}
return `<symbol id="${perfix}-${dirent.name.replace('.svg', '')}" ${content}>`
})
.replace('</svg>', '</symbol>')
svgRes.push(svg)
}
}
return svgRes
}
继续定义一个构建svg元素的函数,在页面body标签内,插入所有svg文件的内容,本函数将在运行Vite build/dev
时执行,修改Vite
生成的html代码。
export const svgBuilder = (path: string, perfix: string = 'local') => {
if (path === '') return
// 使用以上定义的查找svg函数,获得所有的.svg文件内容
const res = findSvgFile(path, perfix)
return {
name: 'svg-transform',
transformIndexHtml(html: string) {
return html.replace(
'<body>',
`
<body>
<svg id="local-icon" data-icon-name="${iconNames.join(
','
)}" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0">
${res.join('')}
</svg>
`
)
},
}
}
第二步:使用读取好的SVG文件
我们在vite.config.ts
文件中,导入我们刚刚写好的函数:
import type { UserConfig } from 'vite'
import { svgBuilder } from '/@/components/icon/svg/index'
随后,在该文件的viteConfig
的plugins
中,添加svg构建函数的使用,使得Vite构建时,可以自动加载好svg文件:
// 示例代码,请注意如果有其他的 plugin 请自行添加
const viteConfig = (): UserConfig => {
// ...
return {
plugins: [svgBuilder('./src/assets/icons/')],
}
}
export default viteConfig
第三步:SVG的显示组件实现
这个就非常简单了,使用svg标签,利用上面构建好的的svg内容的symbol
元素的id属性,直接就可以显示,代码位置:/src/components/icon/svg/index.vue
<template>
<svg class="svg-icon icon" :style="iconStyle">
<use :href="iconName" />
</svg>
</template>
<script setup lang="ts">
import { computed, CSSProperties } from 'vue'
interface Props {
// 图标文件名
name: string
// 图标大小
size: string
// 图标颜色
color: string
}
const props = withDefaults(defineProps<Props>(), {
name: '',
size: '18px',
color: '#000000',
})
const s = `${props.size.replace('px', '')}px`
const iconName = computed(() => `#${props.name}`)
const iconStyle = computed((): CSSProperties => {
return {
color: props.color,
fontSize: s,
}
})
const urlIconStyle = computed(() => {
return {
width: s,
height: s,
mask: `url(${props.name}) no-repeat 50% 50%`,
'-webkit-mask': `url(${props.name}) no-repeat 50% 50%`,
}
})
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
fill: currentColor;
overflow: hidden;
}
</style>
此时,本地svg图标的准备工作就已经完成了,但还不能以<Icon name="local-图标文件名" />
的语法使用图标,因为Icon
组件还没有实现,它的实现,我们放在了文章结尾。
ElementPlus的Icon
首先使用 ElementPlus 官方提供的方法全局注册所有图标组件,然后和Icon组件整合,实现直接以:<Icon name="el-icon-图标名" />
的语法使用图标。
第一步:安装图标库
npm install @element-plus/icons-vue
第二步:准备注册图标的函数
我们在/src/utils/common.ts
文件中,提前准备了注册所有图标的函数,以供main.ts
中直接使用。
import * as elIcons from '@element-plus/icons-vue'
/*
* 全局注册element Plus的icon
*/
export function registerIcons(app: App) {
const icons = elIcons as any
for (const i in icons) {
app.component(`el-icon-${icons[i].name}`, icons[i])
}
}
第三步:在main.ts中注册图标
import { createApp } from 'vue'
const app = createApp(App)
registerIcons(app)
// ...
app.mount('#app')
此时,element Plus图标的准备工作就已经完成了,但还不能以<Icon name="el-icon-图标名" />
的语法使用图标,因为Icon
组件还没有实现,它的实现,我们放在了文章结尾。
Iconfont 和 FontAwesome 图标
我们在/src/utils/common.ts
文件中,提前准备了加载css文件
的函数。
/*
* 加载网络css文件
*/
export function loadCss(url: string): void {
const link = document.createElement('link')
link.rel = 'stylesheet'
link.href = url
link.crossOrigin = 'anonymous'
document.getElementsByTagName('head')[0].appendChild(link)
}
本函数实现向页面动态插入link
标签,将传递的url
参数的资源,加载到网页内,Iconfont和FontAwesome的资源,可以直接通过此函数载入到网页,示例如下:
/src/App.vue
文件
<script setup lang="ts">
import { onMounted } from 'vue'
import { loadCss } from '/@/utils/common'
onMounted(() => {
// 加载 FontAwesome 所有图标,Url由官网提供
loadCss('//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css')
})
</script>
你可以通过以上函数,实现FontAwesome、Iconfont的图标资源(Font class)加载。
Icon组件的实现
通过上面的准备工作,我们终于将四种图标全部都引入到了我们的环境内,接下来,只需要通过Icon组件,统一显示图标的语法即可。
实现非常简单,判断传递的name
属性,确定是何种类的的图标,然后创建VNode
渲染即可。
<script lang="ts">
import { createVNode, resolveComponent, defineComponent, computed, CSSProperties } from 'vue'
import svg from '/@/components/icon/svg/index.vue'
export default defineComponent({
name: 'Icon',
props: {
name: {
type: String,
required: true,
},
size: {
type: String,
default: '18px',
},
color: {
type: String,
default: '#000000',
},
},
setup(props) {
const iconStyle = computed((): CSSProperties => {
const { size, color } = props
let s = `${size.replace('px', '')}px`
return {
fontSize: s,
color: color,
}
})
if (props.name.indexOf('el-icon-') === 0) {
return () => createVNode('el-icon', { class: 'icon el-icon', style: iconStyle.value }, [createVNode(resolveComponent(props.name))])
} else if (props.name.indexOf('local-') === 0) {
return () => createVNode(svg, { name: props.name, size: props.size, color: props.color })
} else {
return () => createVNode('i', { class: [props.name, 'icon'], style: iconStyle.value })
}
},
})
</script>
图标选择器
可以加载出以上四种图标库的所有可用图标,方便直接选择使用,我们的加载方式是通过解析css文件内容获取图标名称(非手动列出图标名称等),此文篇幅有限,我们将在下一篇文章,继续详细讲解图标选择器的实现。
完整代码
支持四种图标的 Icon
组件和 图标选择器组件
,是为BuildAdmin
实现的,所以可以直接在仓库中找到组件的完整代码,另外我们还实现了CRUD代码生成,内置WEB终端,基于ThinkPHP6+Vue3(setup)+Vite+Pinia+
等,免费开源,无需授权即可商用,欢迎大家体验和提出意见建议。
转载自:https://juejin.cn/post/7106427951012380679