使用方法
xml
<!-- 本地图标 -->
<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" />
说明
在闲暇学习中有使用到Iconfont
(阿里巴巴矢量图标库),同时也使用了ElementPlus
的icon,就突然想到之前的项目中还有封装了本地SVG
的组件方法。萌发了一个想法能不能进行一次封装用一个组件把它们都合并起来一起使用呢。当然我有这个想法的时候说明应该很多人都会有这个想法 这个时候只需要输入对的关键词就可以有很好的收获。果然啊,我这个猜想才是最伟大的猜想,于是乎我发现了妙码生花
的BuildAdmin的项目中就已经搞定了。这个时候就只需要借鉴一下即可啦。随后我发现大佬已经在掘金中写出方法这个是链接 好了 话不多说 开始
实现方法
- 本地SVG图标:直接将svg文件放入指定的文件夹内,实现自动加载该文件夹所有的svg,并利用Icon组件直接使用,无需手动import。
- ElementPlus的icon,首先使用官方提供的方法全局注册,然后和Icon组件整合,实现语法的兼容性。
- Iconfont(阿里巴巴矢量图标库),实现了自动载入Font clas(css链接,载入后即可通过class来使用对应的字体图标),实现Icon组件的语法兼容性,然后自动解析出Font class内的所有图标名称,以供图标选择器使用。
- FontAwesome,这是一款很常用的图标库,包含了675个图标,Icon组件实现了自动加载,语法兼容;并且自动解析所有图标名称,以供图标选择器使用。
目录结构
- 我们准备了一个目录/src/assets/icons,将svg文件放入其中,实现自动加载全部文件(不依赖第三方包),并直接以:
<Icon name="local-图标文件名" />
的语法使用图标。 - 创建
/src/components/icon/svg/index.ts
, SVG文件读取准备
typescript
// 从fs库导入读取文件和读取文件夹的函数
// fs库是node自带的,无需通过npm进行install
import { readFileSync, readdirSync } from 'fs'
// 定义一个变量以保存所有的icon文件名称
let iconNames: string[] = []
const svgTitle = /<svg([^>+].*?)>/
const clearHeightWidth = /(width|height)="([^>+].*?)"/g
const hasViewBox = /(viewBox="[^>+].*?")/g
const clearReturn = /(\r)|(\n)/g
// 清理SVG的 fill
const clearFill = /(fill="[^>+].*?")/g
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
}
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>
`
)
},
}
}
findSvgFile: 查找SVG文件的函数(递归),将svg文件内容构建为symbol元素
svgBuilder: 构建svg元素的函数,在页面body标签内,插入所有svg文件的内容,本函数将在运行Vite build/dev时执行,修改Vite生成的html代码
- 在
vite.config.ts
文件中,导入我们刚刚写好的函数
typescript
import type { UserConfig } from 'vite'
import { svgBuilder } from '@/components/icon/svg/index'
export default ({ mode }) => {
...
return defineConfig({
plugins: [
vue(),
svgBuilder('./src/assets/icons/'),
...
]
...
})
}
这里写一个点 就是我在vite.config.ts
导入的时候svgBuilder
的时候出现警告 "该目录不在项目的文件列表中,项目必须列户所有文件,或使用"include"模式" 我的解决方法是在更目录下的tsconfig.node.json
中添加src/components/icon/svg/index.ts
json
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts", "src/components/icon/svg/index.ts"]
}
如果没有出现警告或正常的话 可以无视这个
- 创建
/src/components/icon/svg/index.vue
使用svg标签,利用上面构建好的的svg内容的symbol元素的id属性,直接就可以显示
xml
<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-图标文件名" />
的语法使用图标
-
下面到
ElementPlus的Icon
的准备,引入那些就不描述的了根据ElementPlus的文档进行安装了 -
创建
/src/utils/common.ts
,提前准备了注册所有图标的函数,以供main.ts中直接使用
javascript
// common.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])
}
}
javascript
// main.ts
import { createApp } from 'vue'
const app = createApp(App)
registerIcons(app)
// ...
app.mount('#app')
这个时候element Plus图标的准备工作就已经完成了 也还不能使用
Iconfont
和FontAwesome
图标的准备工作/src/utils/common.ts
文件中,提前准备了加载css文件的函数。
ini
/*
* 加载网络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)
}
loadCss: 向页面动态插入link标签,将传递的url参数的资源,加载到网页内,Iconfont和FontAwesome的资源,可以直接通过此函数载入到网页
/src/App.vue
xml
<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渲染即可
php
<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>
到这里就结束了 到这里就已经可以正常使用了 原篇中还将讲了图标选择器
,有兴趣的可以去参考。