5、基于svg-sprite的图标组件 -- 渐进式vue3的组件库通关秘籍

149 阅读4分钟

本节是渐进式vue3的组件库通关秘籍的第五节 -- 基于svg-sprite的图标组件。 更多可以看这里:vue3组件库开发学习专栏

0、前言

前端实现图标通常有以下几种技术:

  1. 使用图标字体:将图标设计成字体文件,然后通过CSS的字体属性来使用。常用的图标字体包括 Font Awesome、Material Icons等。
  2. SVG 图标:使用矢量图形文件格式 SVG(可伸缩矢量图形)作为图标,通过 <svg> 标签嵌入到HTML代码中。
  3. CSS Sprite:将所有图标合并成一张大图片,然后通过CSS来控制显示区域和位置,实现图标的展示。
  4. Iconfont:通过字体图标库(如阿里巴巴矢量图标库)提供的在线字体图标资源来使用图标。
  5. Base64 编码:将图标图片转换成Base64编码的字符串,然后通过CSS的background属性来使用。

本节介绍的是基于svg的图标使用方案。

1、支持SVG文件作为图标

图标组件需要实现以下功能:

  • 图标组件接受svg文件,使用方式如下:

    import 'path/to/message.svg'
    <Icon name='icon-message' />
    
  • 能够自定义宽高,样式,填充颜色。

总的来说组件的属性如下:

属性名称类型描述默认值
namestringsvg图标文件名称null
styleCSSProperties图标的样式null
widthnumber宽度16
heightnumber高度16
fillstring填充颜色#ffffff

在使用的时候,需要将引入的SVG文件需要使用webpack将原始的svg文件转换成svg-sprit,类似于下面的格式:

<svg>
  <symbol id='icon-test'></symbol>
  <symbol id='icon-home'></symbol>
<svg>

然后我们在只需要在svg标签内使用use,引用上面编译出来的某一个 symbolid 即可。

<svg>
  <use xlink:href='icon-test'/>
<svg>

我们将上面的逻辑封装成组件如下:

新建 components/icon/icon.tsx :

import { defineComponent } from 'vue';
import { props } from './props';
import withInstall from '../../utils/withInstall';
import './style/index.less';
const Icon = defineComponent({
  props,
​
  setup(props) {
    return () => {
      return (
        <svg
          class="svg-icon"
          style={{ width: props.width, height: props.height, ...props.style }}
          aria-hidden="true"
        >
          <use xlinkHref={`#${props.name}`} />
        </svg>
      );
    };
  },
});
export default withInstall(Icon);

新建 components/Icon/props.ts,定义组件的属性:

import type { CSSProperties, PropType } from 'vue';
​
export const props = {
  style: {
    type: Object as PropType<CSSProperties>,
    default: null,
  },
  height: {
    type: Number,
    default: 16,
  },
  width: {
    type: Number,
    default: 16,
  },
  fill: {
    type: String,
    default: '#ffffff',
  },
  name: {
    type: String,
    default: 'home',
  },
};
​
export type IconProps = typeof props;
​

新建 components/icon/index.ts 导出组件:

import icon from './icon';
export default icon;

新建 components/icon/style/index.less ,定义icon的默认样式:

.svg-icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  fill: currentColor;
  overflow: hidden;
}

组件的所有逻辑就如上所示,接下来完成组件的预览和使用。

2、预览和使用

由于组件的使用,需要和svg文件搭配,并且需要将svg文件解析封装成svg-spirte,所以需要以下两个loader

  • svg-sprite-loader : 将svg封装成sprite
  • svgo-loader: 解析svg文件并优化文件大小

安装依赖:

npm i -D svg-sprite-loader svgo-loader

修改 config/webpack.dev.config.js

const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');
const { merge } = require('webpack-merge');
const baseConfig = require('./webpack.base.config');
const distfilename = 'fakeui';
const resolveDir = dir => path.join(__dirname, `../${dir}`);
const dev = merge(baseConfig, {
  ...
  module: {
    rules: [
      {
        test: /.svg$/,
        use: [
          {
            loader: 'svg-sprite-loader',
            options: {
              symbolId: 'icon-[name]',
              include: [path.join(__dirname, 'preview/statics/icons')],
            },
          },
          'svgo-loader',
        ],
      },
    ],
  },
  ...
});module.exports = [dev];

其中 svg-sprite-loader的include表示要解析哪些文件夹下的svg文件。

接下来在app.tsx 中引入看一下效果:

import { defineComponent } from 'vue';
import Icon from '../components/icon';
import './statics/icons/test.svg';
export default defineComponent({
  setup() {
    const render = () => {
      return (
        <>
          <Icon name="icon-test" width={200} height={200} />
        </>
      );
    };
    return render;
  },
});

静态文件大家可以随意找一个svg文件放到 preview/statics/icons 文件夹下。

最终效果如下:

image-20240517163030077

3、内置图标

对于组件库的内置图标,多数的组件库都选择将内置图标单独出来一个库使用,方便增删图标,独立打包,独立发布,易于管理。

对于内置图标,也是通过本节的Icon组件,引入svg文件完成的,所以,对于Icon组件,需要支持传入自身组件的实例,以方便内置图标的引入,也就是说图标的使用方式可以有以下三种:

  • 通过 name 引入 svg 文件
  • 通过 component 引入可复用的图标组件
  • 直接使用可复用的图标组件
import IconTest from 'path/to/icon'
​
<Icon name="icon-test" width={200} height={200} />
<Icon component={IconTest} />
<IconTest />

所以 Icon 组件需要再引入一个 component 属性。

修改 components/Icon/props.ts:声明一个类型为纯函数组件的属性

import type { CSSProperties, PropType } from 'vue';
​
export const props = {
 ...
  // 传入组件,声明为纯函数组件
  component: {
    type: Function,
    default: null,
  },
};
​
export type IconProps = typeof props;
​

修改 preview/app.tsx:

import { defineComponent } from 'vue';
import Icon from '../components/icon';
import './statics/icons/test.svg';
​
const TestIcon = function () {
  return <Icon name="icon-test" width={200} height={200} />;
};
​
export default defineComponent({
  setup() {
    const render = () => {
      return (
        <>
          <Icon component={TestIcon} />
          <TestIcon />
        </>
      );
    };
    return render;
  },
});
​

这里声明了一个纯函数组件,将其作为属性传入 Icon 组件即可,组件能够被正确的渲染。

image-20240520093845406

所以可以通过Icon组件开发一个内置图标库,然后使用的时候再组件库内引入即可。

4、总结

本节实现了一个基于 svg-sprite 的图标组件,能够直接使用svg文件作为图标

使用svg图标主要有以下好处:

  • SVG 图标是矢量图形,无论放大缩小,都可以保持图标的清晰度和锐利度。
  • SVG 图标可以轻松实现多色图标,适用于需要多种颜色的图标场景。
  • 每个 SVG 图标都是一个独立的文件,需要单独加载,可能会导致 HTTP 请求过多,但可以通过雪碧图或者 SVG Sprite 等技术进行优化

参考文献:

本节代码分支:

feature_1.3_icon

欢迎点赞、欢迎star、欢迎讨论、一起学习。

下一节:Loading组件