实现方案
一,对外暴露一个icon组件
为所有icon实现一个icon组件,通过属性(如class/name)等控制具体展示的svg。
webpack下通过 require.context和svg-sprite-loader实现。
组件代码为:
xml
// SvgIcon.vue
<template>
<svg :class="svgClass" aria-hidden="true" v-on="$listeners">
<use :xlink:href="iconName" />
</svg>
</template>
<script>
export default {
name: 'SvgIcon',
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String,
default: ''
}
},
computed: {
iconName() {
return `#icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
} else {
return 'svg-icon'
}
}
}
}
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
将所有svg文件加载到页面
javascript
// icons.js
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'// svg组件
// register globally
Vue.component('svg-icon', SvgIcon)
const req = require.context('./svg', false, /.svg$/)
const requireAll = requireContext => requireContext.keys().map(requireContext)
requireAll(req)
然后在项目入口js中引入
javascript
// main.js
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import './icons'
createApp(App).mount('#app')
然后在页面中直接使用
arduino
//devmo.vue
<template>
<svg-icon icon-class='arrow-left' />
</template>
最终在页面svg加载到html上下文呈现为
使用参考:
在vite下可以通过vite-plugin-svg-icons实现类似的功能,可以参考:vue3 vite2 封装 SVG 图标组件 - 基于 vite 创建 vue3 全家桶续篇
特点是需要将所有的svg都导入,然后通过不同的id渲染展示对应的svg路径
二、每一个svg对外暴露一个组件
每一个svg对外都是一个单独的组件,页面只加载被项目使用到的icon组件。
原理:使用模板生成每一个svg对应的组件以及组件入口文件index.ts。期望的结构如下
arduino
index.ts //组件入口
components
-- IconA.vue // 分类为common,名称为A的icon 组件
-- IconB.vue // 分类为other,名称为B的icon 组件
-- index.ts // components入口文件
-- index.less // icon的默认样式
-- icons-categories.json // 组件的分类json
index.ts文件内容为
typescript
import * as components from './components';
// 定义 install 方法,接收 Vue 作为参数。如果使用 use 注册插件,则所有的组件都将被注册
const install = function (Vue: any) {
// 遍历注册全局组件
for (const key in components) {
Vue.component(key, (components as any)[key]);
}
};
export * from './components';
export default {
// 导出的对象必须具有 install,才能被 Vue.use() 方法安装
install,
...components,
};
components/IconA.vue文件的内容为
xml
<template>
<Icon class="icon" />
</template>
<script setup lang="ts">
import Icon from '../svg/common/a.svg?component';
defineOptions({
name: 'IconA',
});
</script>
加载svg使用的是 vite-svg-loader , 定义组件名使用 unplugin-vue-macros
components/index.ts和components/index.less文件内容为
arduino
// components/index.ts
import './index.less';
export { default as IconA } from './IconA.vue';
export { default as IconB } from './IconB.vue';
// components/index.less
.icon {
overflow: hidden;
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentcolor;
> path{
fill: currentcolor;
}
}
components/icons-categories.json的内容为
json
{
"categories": [
{
"name": "common",
"items": [
"IconA"
]
},
{
"name": "other",
"items": [
"IconB"
]
}
]
}
这个文件主要描述了各个组件属于哪个分类,便于后期的维护和排查问题。以及用来做组件文档展示的依据。
第一步:
对原始的svg去除宽高样式(svg自带的宽高样式会导致外部设置的样式失效),并写入到对应的文件夹中备用
less
const svg = fs.readFileSync(p.join(svgDir,fileName),'utf8');
// 去掉svg图片中的宽高设置,写入到packages/svg/xxx/文件夹中
var result = svg.replace(/(width|height)=['"](\d)+(px)?['"]/g, '');
const svgOutDir = p.join(svgOutRoot, svgOutFolder);
if (fs.existsSync(svgOutDir)) {
fs.writeFileSync(p.join(svgOutDir, fileName), result, 'utf8');
}else{
fs.mkdirSync(svgOutDir);
fs.writeFileSync(p.join(svgOutDir, fileName), result, 'utf8');
}
第二步:
利用组件模板com-template.txt创建对应的svg组件文件
xml
//com-template.txt
<template>
<Icon class="icon" />
</template>
<script setup lang="ts">
import Icon from '../svg/${folder}/${svgName}.svg?component';
defineOptions({
name: '${ComponentName}',
});
</script>
替换模板中的 <math xmlns="http://www.w3.org/1998/Math/MathML"> f o l d e r ∗ ∗ 、 ∗ ∗ {folder}** 、 ** </math>folder∗∗、∗∗{svgName} 和 ${ComponentName} 为真实的svg(上一步处理过后的svg)文件路径和组件名称,然后写入组件位置,如"packages/components/xxx.vue"。
并生成对应的components/index.ts文件内容
第三步:
生成组件分类文件icons-categories.json
结论
最终选择了第二种方式,主要原因是
- 每一个svg都有单独的组件,便于部分引入,也利于代码压缩
@basicai/icons组件库最终目录结构
arduino
docs // vitePress 文档配置目录
resources // svg资源目录
lib // 组件编译输出目录
scripts // 处理resources资源生成组件到packages的脚本和模板
types // 组件源码自动生成的定义文件
packages // 组件源码
- index.ts //组件入口
- svg // 处理过后的svg资源(resources中的svg去除宽高熟悉)
- demo // 组件文档
-components
-- IconA.vue // 分类为common,名称为A的icon 组件
-- IconB.vue // 分类为other,名称为B的icon 组件
-- index.ts // components入口文件
-- index.less // icon的默认样式
-- icons-categories.json // 组件的分类json
Icon 设计规范
参考 UI 相关规范
组件文档选择
支持Vue3的
-
VuePress 使用示例ant-design-vue
-
VitePress vite官方,使用示例element ui plus。
-
vant-cli 移动端风格,目前没有看到PC端风格
最终选择VitePress,原因是官方文档工具(首选),项目活跃度和star比较高,有成熟的例子(element ui plus)。