今天就要来实作 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 的介面 ,让日後新引入的 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 为例:
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 吧!
最後在实作这边就只是在用 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>
);
};
上面讲完大家可能还是没感受到 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 12 :阵列(array)与链结串列(linked list)
>>: [Day24] Tableau 轻松学 - TabPy 使用方法 1
发挥影响力 随时必备的两个元素:Content & Context 自觉、找镜子、了解与掌握...
今天天内容为灯光、粒子效果的基本介绍! Duration 粒子发射的时间 Looping 设定粒子是...
上一篇我们最後讲到了AWS针对NIST所发展的资安五大面向。今天我们来了解第一个面向IAM。 Ide...
刚开始,我想说点什麽 看过市面上许多解释演算法的资料,有些书搭配图片,有些影片浅显易懂,为了挑战自己...
今天我们要来制作新的专题:物品借用纪录微服务! 在学校,尤其是行政处室,最常出现的状况应该就是「借物...