Skip to content
On this page

SvgIcon 组件开发

自定义 svg 图标,封装成 icon 组件像 element-ui icon 组件似得,通过图标名称来使用图标,也可自定义类名控制。

vue
// 密码图标 名称为自己准备svg文件名称 下面需要loader配置 支持事件
<svg-icon icon-class="password" class-name="custom-class" @click="fn"></svg-icon>
// 支持使用外链的形式引入 svg。例如:
<svg-icon icon-class="https://xxxx.svg />
1
2
3
4

4-1 什么是 Svg Sprite

将多个 svg 打包成 svg-sprite。svg 雪碧图。类似于 CSS 中的 Sprite 技术。图标图形整合在一起,实际呈现的时候准确显示特定图标。 阅读资料

感兴趣的这些文章可以阅读下。

4-2 准备 svg 文件

根目录下创建src/icons目录 将svg图标文件放到@/icons/svg文件下面,svg文件压缩包

image.png

创建文件 src/icons/index.ts 全局注册 svg icon 组件入口 现在还没开发 稍后

typescript
import { App } from 'vue'
import SvgIcon from '@/components/SvgIcon/index.vue'

// 使用require.context 加载./svg目录下所有svg文件
const files = import.meta.globEager<any>("./svg/*.svg")
//如果上面这句不行就把上面这句注释掉,使用下面这句
import'virtual:svg-icons-register'

export default (app: App) => {
  // 全局注册svg-icon组件
  app.component('svg-icon', SvgIcon)
}
1
2
3
4
5
6
7
8
9
10
11
12

如果。globEager 报错有两种试配置 vite/client, 一种可以在 tsconfig.json 中配置 "types": ["vite/client"], 一种是在 shims-vue.d.ts 中添加在 /// 链接在这里 https://vitejs.cn/guide/features.html#client-types 另外还要 globEager 内部是依赖了 fast-glob 模块,也要安装一下 npm install fast-glob --save

src/icons/svgo.yml 配置文件 svgo svg 压缩处理优化配置文件。详情看4-7 svg优化

yaml
plugins:
- removeAttrs:
    attrs:
      - 'fill'
      - 'fill-rule'

1
2
3
4
5
6

4-3 配置 vite-plugin-svg-icons

用来根据导入的 svg 文件自动生成 symbol 标签并插入 html

bash
npm install vite-plugin-svg-icons -D
1

修改 vite.config.js 配置文件

javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path, { resolve } from 'path';
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    createSvgIconsPlugin({
      iconDirs: [path.resolve(process.cwd(), 'src/icons/svg')],
      symbolId: 'icon-[dir]-[name]',
      inject: 'body-last',
      customDomId: '__svg__icons__dom__'
    })
  ],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src')
    }
  }
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

4-4 开发 svg icon 组件

src/components 下创建 SvgIcon/index.vue image.png SvgIcon/index.vue

vue
<template>
  <!-- 如果iconClass是带协议的图标链接 则通过style属性方式渲染-->
  <div
    class="svg-icon svg-external-icon"
    v-if="isExt"
    :style="styleExternalIcon"
    v-bind="$attrs"
  ></div>
  <!-- SVG icon 通过名称使用 -->
  <svg v-else :class="svgClass" aria-hidden="true" v-bind="$attrs">
    <!--
       SVG中的use元素可以调用其他SVG文件的元素,<use xlink:href="#symbolId"></use>
    -->
    <use :xlink:href="iconName" />
  </svg>
</template>

<script setup lang="ts">
import { isExternal } from '@/utils/validate'
import { computed } from 'vue'
const props = defineProps<{ iconClass: string,className?:string }>()
// 是否是带协议的图片链接
const isExt = computed(() => isExternal(props.iconClass || ''))
// 拼接成symbolId 在loader配置中指定了symbolId格式 icon-图标名称
const iconName = computed(() => `#icon-${props.iconClass}`)
// 添加类名 props.className外部传入自定义类名
const svgClass = computed(() =>
  props.className ? `svg-icon ${props.className}` : 'svg-icon'
)
// 如果iconClass是带协议的图标链接 则通过style css属性方式渲染
const styleExternalIcon = computed(() => ({
  mask: `url(${props.iconClass}) no-repeat 50% 50%`,
  '-webkit-mask': `url(${props.iconClass}) no-repeat 50% 50%`
}))
</script>

<style scoped>
.svg-icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  fill: currentColor;
  overflow: hidden;
}
.svg-external-icon {
  background-color: currentColor;
  mask-size: cover !important;
  display: inline-block;
}
</style>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

vue3.0中关于$listeners移除 文档说明

@/utils/validate.ts 工具方法

创建 src/utils/validate.ts

typescript
// 判断路径是不是带协议的外链
export const isExternal = (path: string): boolean => {
  return /^(https?:|mailto:|tel:)/.test(path)
}
1
2
3
4

4-5 main.ts 通过 use 安装 icon 组件

main.ts 导入 src/icons/index.ts 并 use 安装

typescript
import { createApp } from 'vue'
import router from './router/index';
import ElementPlus from 'element-plus';
import store from './store'
import App from './App.vue'
import 'normalize.css/normalize.css'
import '@/styles/index.scss'
import 'virtual:svg-icons-register'
import initSvgIcon from '@/icons/index'
createApp(App)
    .use(store)
    .use(ElementPlus)
    .use(initSvgIcon)
    .use(router)
    .mount('#app')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

4-6 简单引用下

在 views/dashboard/index.vue 试用下

vue
<template>
  <div>
    <h1>Dashboard page</h1>
    <svg-icon icon-class="bug"></svg-icon>
    <!-- icon-class svg图标名称 class-name 额外的自定义类名 @click绑定事件 -->
    <svg-icon icon-class="404" class-name="custom-class" @click="sayHi"></svg-icon>
  </div>
</template>

<script setup  lang="ts">
const sayHi = () => {
   alert('hi svg')
}
</script>
<style lang="scss">
  .custom-class { // 自定义样式404
    font-size: 200px;
    color: green;
  }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

icon-class 分别是我们 src/icons/svg 里面的图标的名称 image.png

最终效果 image.png 点击 404icon 出来弹窗 image.png

4-7 Svg 优化

svgo是svg 压缩处理优化工具。我们很多网上下载或者Sketch导出的 svg 会有很多冗余无用的信息,大大的增加了 svg 的尺寸,我们可以使用svgo对它进行优化。

我们在创建 src/icons/svgo.yml 配置文件 安装 svgo,注意需要指定版本号

bash
npm i -D svgo@1.3.2
1

package.json 添加 npm scripts

json
{
  ...
  "scripts": {
    ...,
    "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
  },
}
1
2
3
4
5
6
7

运行 npm run svgo 压缩优化 svgo地址 https://github.com/svg/svgo

4-8 tsconfig.json 配置

javascript
{
  "compilerOptions": {
   "target": "esnext",
   "module": "esnext",
   "moduleResolution": "node",
   "esModuleInterop": true,
   "forceConsistentCasingInFileNames": true,
   "strict": true,
   "skipLibCheck": true,
   "types": ["vite/client"],
    "baseUrl": ".",
   "paths": {
     "@/*": ["src/*"]
   }
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

4-9 自动注册路由(选做)

可以使用 vite-plugin-pages 插件自动注册路由 vite-plugin-pages 会自动把 pages 目录中的 .vue 和 .md 文件生成对应的路由,并且我们可以利用 markdown 的 front-matter 来为路由提供一些额外信息

第一步安装相关的模块

npm install vite-plugin-pages @types/fs-extra @types/node fs-extra gray-matter -D

然后修改 vite.config.js 配置文件

javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path, { resolve } from 'path';
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import VitePages from "vite-plugin-pages";
import fs from "fs-extra";
//gray-matter 的功能,可以获取相关文件中的 front-matter,并将其处理为一个对象
import matter from "gray-matter";
export default defineConfig({
  plugins: [
    vue(),
    createSvgIconsPlugin({
      iconDirs: [path.resolve(process.cwd(), 'src/icons/svg')],
      symbolId: 'icon-[dir]-[name]',
      inject: 'body-last',
      customDomId: '__svg__icons__dom__'
    }),
    VitePages({
      extensions: ["vue", "md"],//需要包含的文件类型,这里显然是 .vue 和 .md 文件
      pagesDir: "src/views",//寻找文件的目录,这里选择了项目根目录下的 views 目录
      extendRoute(route) {//提供一个方法,对每个文件产生路由做一些加工,这里是对 route.meta 的处理
        const path = resolve(__dirname, route.component.slice(1));
        const md = fs.readFileSync(path, "utf-8");
        const { data } = matter(md);
        route.meta = Object.assign(route.meta || {}, { frontmatter: data });
        return route;
      },
    }),
  ],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src')
    }
  }
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

再修改路由配置

参考链接src\router\index.ts

javascript
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
import Layout from '@/layout/index.vue'
import children from "pages-generated"; // vite-plugin-pages 生成的路由信息

const routes: Array<RouteRecordRaw> = [
    {
        path: '/',
        component: Layout,
        redirect: '/dashboard',
        children
    }
]

const router = createRouter({
    history: createWebHashHistory(),
    routes
})

export default router

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

在 TS 中,直接从 pages-generated 导入会引起类型错误,需要在 tsconfig.jsoncompilerOptions.types 数组中加入 vite-plugin-pages/client 来加载对应的声明文件

本节参考源码

https://gitee.com/zhufengpeixun/vue3-admin2

沪ICP备20006251号-1