从零搭建组件Icon组件

719 阅读2分钟

前言


开个大坑,从零搭建一个组件库。这次做一个基础组件Icon。

组件最后的效果是这样的

参考ant-design等知名组件库和相关文章的总结

基础组件库主要按以下分类来划分:

  • 通用型组件: 比如Button, Icon等.

  • 布局型组件: 比如Grid, Layout布局等.

  • 导航型组件: 比如面包屑Breadcrumb, 下拉菜单Dropdown, 菜单Menu等.

  • 数据录入型组件: 比如form表单, Switch开关, Upload文件上传等.

  • 数据展示型组件: 比如Avator头像, Table表格, List列表等.

  • 反馈型组件: 比如Progress进度条, Drawer抽屉, Modal对话框等.

成分


外部一个span元素包裹一个svg元素

设计思路


整理出一个如下的期望API表格

参数 说明 类型 默认值 版本
type 设置图标的名称 string -
className 设置图标的样式名 string -
style 设置图标的样式 例如:color,width,height CSSProperties -
color 设置图标的主题颜色 同 css样式中的color string -
height 设置图标的高度 string/number -
width 设置图标的宽度 string/number -
size 设置已规定图标的大小 string md
spin 是否有旋转动画 boolean false
rotate 图标旋转角度 number 0
component 自定义svg ReactComponent -
  • 根据不同的名称(type字段)选择对应icon
  • 能自定义className和style
  • 能够设置主题色
  • 自由改变宽高
  • 自由选择旋转角度
  • 选择是否触发旋转动画
  • 自定义svg功能

对应的设计思维导图 Icon.jpeg

基于React实现一个icon组件


svg的处理

思路:在页面顶部加载一个默认icon组成的类雪碧图,通过<symbol>标签的id属性和<use>标签的xlinkHref来进行一个选择。

优点:避免多次重绘,优化用户体验。

实现:

// 默认icon对象
const icons = (key?: string) => {
  return (
    {
      SimpleDocument:
        '<svg viewBox="0 0 1024 1024"><path d="M838.050909 93.090909A92.858182 92.858182 0 0 1 930.909091 185.949091v652.101818A92.858182 92.858182 0 0 1 838.050909 930.909091H185.949091A92.858182 92.858182 0 0 1 93.090909 838.050909V185.949091A92.858182 92.858182 0 0 1 185.949091 93.090909h652.101818z m0 46.545455H185.949091C160.349091 139.636364 139.636364 160.349091 139.636364 185.949091v652.101818c0 25.6 20.712727 46.312727 46.312727 46.312727h652.101818c25.6 0 46.312727-20.712727 46.312727-46.312727V185.949091C884.363636 160.349091 863.650909 139.636364 838.050909 139.636364zM581.771636 698.181818c11.962182 0 21.806545 8.913455 23.156364 20.549818L605.090909 721.454545c0 12.846545-10.612364 23.272727-23.319273 23.272728h-232.634181a23.249455 23.249455 0 1 1 0-46.545455h232.634181z m0-186.181818c11.962182 0 21.806545 8.913455 23.156364 20.549818L605.090909 535.272727c0 12.846545-10.612364 23.272727-23.319273 23.272728h-232.634181a23.249455 23.249455 0 1 1 0-46.545455h232.634181z m93.114182-186.181818c11.938909 0 21.783273 8.913455 23.133091 20.549818L698.181818 349.090909c0 12.846545-10.356364 23.272727-23.272727 23.272727H349.090909c-12.846545 0-23.272727-10.333091-23.272727-23.272727 0-12.846545 10.356364-23.272727 23.272727-23.272727h325.818182z"  /></svg>'
    }
    )
};  
const svgSprite = (contents: string) => `
  <svg
    xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink"
    id="__SHOW_UI_SVG_SPRITE_NODE__"
    style="display:none;overflow:hidden;width:0;height:0"
  >
    <defs>
      ${contents}
    </defs>
  </svg>
`;

const renderSvgSprite = () => {
  const Icons = icons()
  const symbols = Object.keys(Icons)
    .map(iconName => {
      const svgContent = Icons[iconName].length > 0 && Icons[iconName].split('svg')[1];
      return `<symbol id=${iconName}${svgContent}symbol>`;
    })
    .join('');
  return svgSprite(symbols);
};

const loadSprite = () => {
  if (!document) {
    return;
  }
  const existing = document.getElementById('__SHOW_UI_SVG_SPRITE_NODE__');
  const mountNode = document.body;

  if (!existing) {
    mountNode.insertAdjacentHTML('afterbegin', renderSvgSprite());
  }
};

export default loadSprite;

svg相关props

export interface svgProps {
  size?: string,
  width?: string | number,
  height?: string | number
}

Icon组件props

export type IconBaseProps = Omit<React.HTMLProps<HTMLSpanElement>,'size'>

export interface IconProps extends IconBaseProps, svgProps {
  type?: string,
  spin?: boolean,
  rotate?: number,
  color?: string,
  component?: Element
}

外部ref功能传入的实现

const Icon = React.forwardRef<HTMLSpanElement, IconProps>((props, ref) => {
  //...
})

默认样式,自定义svg的实现

  const classString = classnames(
    'show-icon',
    className
  )
  const renderInnerNode = () => {
    return (
        Component
    )  
  }

  return (
    <span
      role="img"
      {...restProps}
      ref={ref}
      onClick={onClick}
      className={classString}
    >
      {
        Component ?
          renderInnerNode() : 
          <svg className={svgClassString} style={svgStyle}>
            <use xlinkHref={`#${type}`} />
          </svg>
      }
    </span>
  );
})

svg旋转角度,宽高,和主题色功能的实现

  const svgStyle = {
    msTransform: `rotate(${rotate}deg)`,
    transform: `rotate(${rotate}deg)`,
    width,
    height,
    color
 }

默认属性值的实现

Icon.defaultProps = {
  rotate: 0,
  size: 'md',
  width: '',
  height: ''
}

相关css样式

.show-icon {
    display: inline-block;
    color: inherit;
    font-style: normal;
    line-height: 0;
    text-align: center;
    text-transform: none;
    vertical-align: -.125em;
    text-rendering: optimizeLegibility;
    -webkit-font-smoothing: antialiased;

    &-svg{
        fill: currentColor;
        background-size: cover;
        width: 100%;
        height: 100%;
    }

    &-md{
        width: @md-icon-size;
        height: @md-icon-size;
    }

    &-spin {
        animation: cirle-anim 1s linear infinite;
    }

    @keyframes cirle-anim {
        100% {
          transform: rotate(360deg);
        }
      }
}