Vue3中使用各类字体图标:本地SVG、Iconfont、FontAwesome、ElementPlus

使用方法

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的项目中就已经搞定了。这个时候就只需要借鉴一下即可啦。随后我发现大佬已经在掘金中写出方法这个是链接 好了 话不多说 开始

实现方法

  1. 本地SVG图标:直接将svg文件放入指定的文件夹内,实现自动加载该文件夹所有的svg,并利用Icon组件直接使用,无需手动import。
  2. ElementPlus的icon,首先使用官方提供的方法全局注册,然后和Icon组件整合,实现语法的兼容性。
  3. Iconfont(阿里巴巴矢量图标库),实现了自动载入Font clas(css链接,载入后即可通过class来使用对应的字体图标),实现Icon组件的语法兼容性,然后自动解析出Font class内的所有图标名称,以供图标选择器使用。
  4. 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图标的准备工作就已经完成了 也还不能使用

  • IconfontFontAwesome 图标的准备工作 /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>

到这里就结束了 到这里就已经可以正常使用了 原篇中还将讲了图标选择器,有兴趣的可以去参考。

相关推荐
Devil枫4 小时前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试
GIS程序媛—椰子5 小时前
【Vue 全家桶】6、vue-router 路由(更新中)
前端·vue.js
毕业设计制作和分享6 小时前
ssm《数据库系统原理》课程平台的设计与实现+vue
前端·数据库·vue.js·oracle·mybatis
程序媛小果6 小时前
基于java+SpringBoot+Vue的旅游管理系统设计与实现
java·vue.js·spring boot
从兄7 小时前
vue 使用docx-preview 预览替换文档内的特定变量
javascript·vue.js·ecmascript
凉辰8 小时前
设计模式 策略模式 场景Vue (技术提升)
vue.js·设计模式·策略模式
薛一半9 小时前
PC端查看历史消息,鼠标向上滚动加载数据时页面停留在上次查看的位置
前端·javascript·vue.js
MarcoPage10 小时前
第十九课 Vue组件中的方法
前端·javascript·vue.js
工业互联网专业10 小时前
Python毕业设计选题:基于Hadoop的租房数据分析系统的设计与实现
vue.js·hadoop·python·flask·毕业设计·源码·课程设计
你好龙卷风!!!10 小时前
vue3 怎么判断数据列是否包某一列名
前端·javascript·vue.js