Svg icon组件库设计方案

实现方案

一,对外暴露一个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上下文呈现为

使用参考:

手摸手,带你优雅的使用 icon

vue中封装svg-icon组件并使用

在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的

最终选择VitePress,原因是官方文档工具(首选),项目活跃度和star比较高,有成熟的例子(element ui plus)。

相关推荐
光影少年7 分钟前
vue2与vue3的全局通信插件,如何实现自定义的插件
前端·javascript·vue.js
As977_8 分钟前
前端学习Day12 CSS盒子的定位(相对定位篇“附练习”)
前端·css·学习
susu108301891110 分钟前
vue3 css的样式如果background没有,如何覆盖有background的样式
前端·css
Ocean☾12 分钟前
前端基础-html-注册界面
前端·算法·html
Rattenking12 分钟前
React 源码学习01 ---- React.Children.map 的实现与应用
javascript·学习·react.js
Dragon Wu14 分钟前
前端 Canvas 绘画 总结
前端
CodeToGym19 分钟前
Webpack性能优化指南:从构建到部署的全方位策略
前端·webpack·性能优化
~甲壳虫20 分钟前
说说webpack中常见的Loader?解决了什么问题?
前端·webpack·node.js
~甲壳虫24 分钟前
说说webpack proxy工作原理?为什么能解决跨域
前端·webpack·node.js
Cwhat25 分钟前
前端性能优化2
前端