iT邦幫忙

2021 iThome 鐵人賽

DAY 10
0
Modern Web

關於 UI 元件你所該知道的事系列 第 10

Day 10 - Design System x 實作 — Icon 元件

https://ithelp.ithome.com.tw/upload/images/20210925/201207549Gd7QN7Xfv.png
今天就要來實作 Icon 啦!事不宜遲直接開始!

想先看 Code 或是 Demo 的由此去:
Github Repo: ithelp-ui-demo
Live Demo:Storybook

在 Icon 這邊首先要建立一個觀念,就是 「網頁中的 Icon 基本上都會是以 SVG 的格式來呈現」
使用 SVG 的原因主要是用來減少圖片的使用率,透過向量圖形把 Icon 畫出來並當成文字來調整大小 (font-size)、和顏色 (color)。

更詳細的理由可以看 MDN - 為何使用 SVG ?

而在這邊能使用 font-size 來改變大小的原因是因為我在寬高是使用 1em 來設定,em 的意思是以父元素的倍數來呈現,因此在父元素指定 font-size 就能改變 Icon 大小啦,而沒給的話就是吃到最頂層預設的值。
參考 一次搞懂 CSS 字體單位:px、em、rem 和 %

那既然都是用 SVG 來 Render 的話,其實就可以把 SVG 會用到的屬性統一出一個規格,再透過框架各自的語法把它 Render 出來。

順著這個邏輯,以下會來逐步介紹 IconDefinition、Icon interface 和 Icon Component,帶大家一探 Icon 是如何被元件化的!

IconDefinition

首先來看看能如何定義一個 IconDefinition 的介面 ,讓日後新引入的 Icon 都能遵循這個格式來繪製。

export interface IconDefinition {
  name: string;
  definition: {
    svg?: {
      viewBox?: string; // default: 0 0 24 24
    };
    path?: {
      d?: string;
      fill?: string;
      fillRule?: 'nonzero' | 'evenodd' | 'inherit';
      stroke?: string;
      strokeWidth?: string | number;
      transform?: string;
    };
  };
}

接著直接來看看一個 Icon 能怎麼透過 SVG 畫出來,以 check 為例:

https://ithelp.ithome.com.tw/upload/images/20210925/201207549rnXBUSON6.png

import { IconDefinition } from "./typings";

export const CheckIcon: IconDefinition = {
  name: "check",
  definition: {
    svg: {
      viewBox: "0 0 24 24",
    },
    path: {
      fill: "currentColor",
      fillRule: "evenodd",
      stroke: "none",
      strokeWidth: 1,
      d:
        "M17.993 8.768l-7.625 7.625L6.4 12.425l1.061-1.061 2.908 2.907 6.564-6.563z",
    },
  },
};

以此就可以知道其實要用 SVG 畫出一個 Icon 的話只需要像上面這樣定義,主要就是 viewBox 確認比例,然後設定一下 fill、fillRule、stroke、strokeWidth,接著運用 d 這個屬性把線都畫出來。

那我之前的經驗是可以請 UI 設計師遵從 viewBox: "0 0 24 24" 這樣的比例把 SVG 切出來給我,如此統一規格之後,未來就可以無痛新增 Icon 了。

想看看其他 Icon 的 Definition 請參照 Github

這邊在簡短總結一下 IconDefinition :「藉由制定一套 Icon 的 SVG 介面,日後想新增客製化的 Icon 只要照著這介面去繪製即可。」

既然統一了 Icon 的 SVG 規格之後,我們就可以接著來看看 Icon 這個元件的 Interface 和在 React 上如何實作它了!

介面

最大的重點在 IconDefinition 都說完了,剩餘的兩個屬性挺白話的,就直接看 Storybook 的 Description 吧!

https://ithelp.ithome.com.tw/upload/images/20210925/20120754oqPC7B3cBv.png

元件實作

最後在實作這邊就只是在用 React JSX 的語法把 IconDefinition 丟下去 Render 出對應的 Icon 而已。

export const Icon: React.FC<IconProps> = (props) => {
  const { className, color = "black", icon, spin = false } = props;
  const { definition } = icon;

  return (
    <i
      aria-hidden
      className={`
        inline-block flex-shrink-0 select-none w-em h-em
        ${spin ? "animate-spin" : ""}
        ${color ? Color[color] : ""}
        ${className ? className : ""}
      `}
      data-icon-name={icon.name}
    >
      <svg {...definition.svg} focusable={false}>
        <path {...definition.path} />
      </svg>
    </i>
  );
};

加碼:看看 Angular 如何運用同一套 IconDefinition 實作 Icon 元件

上面講完大家可能還是沒感受到 IconDefinition 的力量,因此這邊直接運用同一套 Definition 來用 Angular 的 Template 語法上去呈現。

template: `
    <svg
      [attr.focusable]="false"
      [attr.viewBox]="svg?.viewBox"
    >
      <svg:path
        *ngIf="path"
        [attr.d]="path.d"
        [attr.fill]="path.fill"
        [attr.fill-rule]="path.fillRule"
        [attr.stroke]="path.stroke"
        [attr.stroke-width]="path.strokeWidth"
        [attr.transform]="path.transform"
      >
      </svg:path>
    </svg>
  `,

get svg() {
  return this.icon.definition.svg;
}

get path() {
  return this.icon.definition.path;
}

由此可知,有時候介面定義出來,不同框架就只是在用不同的語法去實作而已。

小結

相信介紹完 Icon 之後,大家會對介面的定義能更有感觸,我第一次知道 Icon 能用這樣的方式來產出來的時候,也是驚為天人呢,沒想到可以幫一個個 Icon 抽象化成這樣的 SVG 格式!

明天要介紹的事在 Design System 的 System 實作上的最後一個元素 — 元件動畫(Motion)的過渡(Transition)。

如果覺得對你有幫助的話,希望大家可以不吝在 Github Repo 上給個 Star ><

那就明天見囉!


上一篇
Day 09 - Design System x 實作 — Typography
下一篇
Day 11 - Design System x 實作 — Transition
系列文
關於 UI 元件你所該知道的事30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言