Shore Icon
Mar 01, 2022
2022
在 Shore 的工作时间不长,但我做了一套风格现代、维护成本可控的图标系统(Icon System)。过程中主要参考了刚完成重构的 Spotify Icon System、微软的 Microsoft Fluent System Icons,以及业内的 SF Symbols。这套系统在风格管理、版本管理、跨端表现和自动化交付上都更顺手,我也希望把其中的经验留给正在从零搭建或准备重构图标系统的团队与设计师。
痛点
要做一套好用的图标系统,风格统一、识别清晰、交付与管理简单——听起来是共识,真正落地时每一环都可能变成痛点。
风格统一
图标能不能称为「系统」,第一条往往是够不够统一。我们以前的做法,是在统一尺寸的画布上,用一套网格约束、几档规范的圆角和统一的描边粗细去框图形。这样顶多统一了表层样式;若网格卡得太死,执行时很容易出现「多一格嫌长、少一格嫌短」的别扭,反而违背视觉直觉。
Material 与 Ant Design 的图标网格
识别清晰
图标传达内容,不只靠外形,还与最小可用尺寸、正负形是否一致有关;这些细节会直接影响识别,也容易制造视觉错觉。
交付与管理
把多人绘制的图标收成一套资源,本身就很费神。好在 Figma 能让更多人在同一份设计稿里协作,历史版本也算能管。但设计稿的版本并不等于研发最终接手的版本,两者很容易脱节。每次还要按端单独导出格式,若没有专人维护,输出就会越来越乱,最后线上各端各版本、各画各的图标。
阿里巴巴的 iconfont 在上一代工具里很好地缓解过这些问题,但以今天的协作与工程节奏来看,已经明显不够用了。
发现与学习
SF Symbols
门槛最低:多数设计师的 Mac 上都装得到 SF Symbols。细读之后会发现,无论从哪个维度看,这套图标都相当出色。
SF 的图标与 SF Pro 是一起为混排设计的,因此字形的中线、基线也会进图标参考;字体没有「裁剪内容」一类的包袱,边缘留多少不必纠结。每个字重都有对应图标,第一眼就很震撼。3.0 起又补了大量彩色图标,同样基于彩色字体。
我把 SF 放进 Figma 看路径时发现:每个字重下还有 5 档相对宽度,而且宽度变化是线性的,并不是随便挑几个档位——多半是为了让描边粗细在观感上更均匀。另一个细节是转折与交叉处的「收口」,这是字形里常用的手法,让弯折处看起来和直线段一样稳,小尺寸也更清楚;在成千上万枚图标里坚持这样做,非常难得。
Microsoft Fluent System Icons
Microsoft Fluent System Icons →
微软更慷慨一些,把整套图标系统设计稿放在 Figma 社区里,规范也写得清楚,很适合当教材。
Fluent 讲了网格、不同类型外形的画法、怎么测视觉权重、填充图标怎么处理,以及角标不要挡关键信息——而不只是「铺一层底色」那种粗暴做法,等等。
Spotify Icon System
Refreshing our Icon System: the why and how behind the changes →
在我看来,Spotify 很擅长用较低成本换较大收益,跨端设计系统就是例子;这次图标系统更新也延续了同一套思路。
新系统用两种线宽覆盖两类尺寸区间,并对不同线宽做不同细化——相比 SF 为每个字重各做一整套图标,更贴近「资源没有苹果那么宽裕」的普通团队。
Shore Icon System
有了前面的输入,我想做一套自己的图标系统:视觉统一、识别清楚、手感平衡,还要有一条顺手的交付链路。把整套库做成能和文字自然混排的字体,对我来说不现实,维护和绘制成本都太高,最后还是落在更主流的 SVG 上。不同的是我选了 SVG Symbol 这种单文件、多图形的组织方式,导出则依赖我自研的 Figma 插件 VectorS。
多重线宽
我借鉴了 SF 的多档线宽思路,在 24×24 的格子里找出视觉平衡的 5 档宽度,并为每一档写了使用说明。
为了批量整理图标在 Frame 里的适配规则、少手填线宽,我在 VectorS 里加了一串小工具做样式填充,线宽这一层就这样收住了。
非严格的网格
很多第三方图标不如官方图标「舒服」,很大一部分原因是对网格的理解:网格应是参照,而不是枷锁;在网格给定的范围里画出舒服的形,比处处贴齐更重要。各家的官方图标也不是死板对齐,而是灵活运用对齐与对比。
我画了一套偏「非严格」的辅助网格,定形之后再配合模糊看视觉权重。
非零与偶奇填充
非零规则(non-zero)与偶奇规则(even-odd)对应 SVG 的 fill-rule,描述的是路径布尔运算怎么算。简单说,要么按路径的绕向自动运算,要么靠标记规则;前者兼容性更好(字体、Android 矢量等),后者在 Web 与 iOS 上往往更可控。
Figma 默认更接近 even-odd。用过 Figma 导出到 iconfont 的人多半知道 Fill Rule Editor——它通过调整路径方向,让结果符合 non-zero 规则。作者曾是 Figma 的前 CTO,不知是否因此,社区里几乎找不到第二个做同类事的插件。
为跨端考虑,Shore Icon 的填充规则我选了兼容性更好的 non-zero。
统一输出格式
格式统一同样是跨端前提。我用插件导出两种格式,给研发同事评估怎么消费。
SVG Symbol 格式预览
<svg style="display: none;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <!-- 3 icons / 2022/3/18上午1:31:10 / VectorS 1.0.0 --> <symbol id="Figma" viewbox="0 0 24 24"> <g transform="translate(5.1 2.1)"> <path fill-rule="evenodd" clip-rule="evenodd" d="M 3.9 0 L 9.9 0 C 12.053 0 13.8 1.747 13.8 3.9 C 13.8 5.106 13.252 6.185 12.391 6.9 C 13.252 7.616 13.8 8.694 13.8 9.9 C 13.8 12.053 12.053 13.8 9.9 13.8 C 9.127 13.8 8.406 13.575 7.8 13.186 L 7.8 15.9 C 7.8 18.053 6.053 19.8 3.9 19.8 C 1.747 19.8 0 18.053 0 15.9 C 0 14.694 0.548 13.616 1.409 12.9 C 0.548 12.184 0 11.106 0 9.9 C 0 8.694 0.548 7.616 1.409 6.9 C 0.548 6.185 0 5.106 0 3.9 C 0 1.747 1.747 0 3.9 0 Z M 1.8 3.9 C 1.8 2.741 2.741 1.8 3.9 1.8 L 6 1.8 L 6 6 L 3.9 6 C 2.741 6 1.8 5.059 1.8 3.9 Z M 6 9.902 C 6 9.901 6 9.901 6 9.9 C 6 9.899 6 9.899 6 9.898 L 6 7.8 L 3.9 7.8 C 2.741 7.8 1.8 8.741 1.8 9.9 C 1.8 11.059 2.741 12 3.9 12 L 6 12 L 6 9.902 Z M 7.8 9.9 C 7.8 11.059 8.741 12 9.9 12 C 11.059 12 12 11.059 12 9.9 C 12 8.741 11.059 7.8 9.9 7.8 C 8.741 7.8 7.8 8.741 7.8 9.9 Z M 9.9 6 C 11.059 6 12 5.059 12 3.9 C 12 2.741 11.059 1.8 9.9 1.8 L 7.8 1.8 L 7.8 6 L 9.9 6 Z M 1.8 15.9 C 1.8 14.741 2.741 13.8 3.9 13.8 L 6 13.8 L 6 15.9 C 6 17.059 5.059 18 3.9 18 C 2.741 18 1.8 17.059 1.8 15.9 Z" fill="rgb(0,0,0)" /> </g> </symbol> <symbol id="Twitter" viewbox="0 0 24 24"> <g transform="translate(2.5 4)"> <path d="M 17.048 3.984 C 17.055 4.16 17.055 4.328 17.055 4.504 C 17.063 9.84 13.143 16 5.973 16 C 3.859 16 1.783 15.368 0 14.184 C 0.309 14.224 0.617 14.24 0.926 14.24 C 2.678 14.24 4.383 13.632 5.765 12.504 C 4.098 12.472 2.632 11.344 2.122 9.696 C 2.709 9.816 3.311 9.792 3.882 9.624 C 2.068 9.256 0.764 7.6 0.756 5.672 C 0.756 5.656 0.756 5.64 0.756 5.624 C 1.297 5.936 1.906 6.112 2.524 6.128 C 0.818 4.944 0.286 2.584 1.32 0.736 C 3.303 3.264 6.22 4.792 9.353 4.96 C 9.037 3.56 9.469 2.088 10.48 1.096 C 12.047 -0.432 14.516 -0.352 15.998 1.272 C 16.87 1.096 17.711 0.76 18.475 0.288 C 18.182 1.224 17.572 2.016 16.762 2.52 C 17.534 2.424 18.29 2.208 19 1.888 C 18.475 2.704 17.812 3.408 17.048 3.984 Z" fill="rgb(0,0,0)" /> </g> </symbol> <symbol id="Instagram" viewbox="0 0 24 24"> <g transform="translate(2 2)"> <path fill-rule="evenodd" clip-rule="evenodd" d="M 9.996 4.865 C 7.163 4.865 4.861 7.163 4.861 10 C 4.861 12.837 7.159 15.135 9.996 15.135 C 12.833 15.135 15.131 12.837 15.131 10 C 15.131 7.163 12.833 4.865 9.996 4.865 Z M 9.996 13.329 C 8.155 13.329 6.663 11.837 6.663 9.996 C 6.663 8.155 8.155 6.663 9.996 6.663 C 11.837 6.663 13.329 8.155 13.329 9.996 C 13.329 11.837 11.837 13.329 9.996 13.329 Z M 16.532 4.659 C 16.532 5.321 15.995 5.857 15.333 5.857 C 14.671 5.857 14.135 5.321 14.135 4.659 C 14.135 3.997 14.671 3.46 15.333 3.46 C 15.995 3.46 16.532 3.997 16.532 4.659 Z M 9.996 0 C 7.282 0 6.94 0.012 5.873 0.06 C 4.81 0.107 4.083 0.278 3.448 0.524 C 2.79 0.778 2.234 1.123 1.679 1.679 C 1.123 2.234 0.782 2.794 0.524 3.448 C 0.278 4.083 0.107 4.81 0.06 5.877 C 0.012 6.94 0 7.282 0 9.996 C 0 12.71 0.012 13.052 0.06 14.119 C 0.107 15.183 0.278 15.909 0.524 16.548 C 0.778 17.206 1.123 17.762 1.679 18.317 C 2.234 18.873 2.794 19.214 3.448 19.472 C 4.083 19.718 4.81 19.889 5.877 19.936 C 6.944 19.984 7.282 19.996 10 19.996 C 12.718 19.996 13.056 19.984 14.123 19.936 C 15.187 19.889 15.913 19.718 16.552 19.472 C 17.21 19.218 17.766 18.873 18.321 18.317 C 18.877 17.762 19.218 17.202 19.476 16.548 C 19.722 15.913 19.893 15.186 19.94 14.119 C 19.988 13.052 20 12.714 20 9.996 C 20 7.278 19.988 6.94 19.94 5.873 C 19.893 4.81 19.722 4.083 19.476 3.444 C 19.222 2.786 18.877 2.23 18.321 1.675 C 17.766 1.119 17.206 0.778 16.552 0.52 C 15.917 0.274 15.19 0.103 14.123 0.056 C 13.052 0.012 12.71 0 9.996 0 Z M 9.996 1.802 C 12.667 1.802 12.98 1.813 14.036 1.861 C 15.012 1.905 15.54 2.067 15.893 2.206 C 16.361 2.389 16.694 2.603 17.044 2.952 C 17.393 3.302 17.611 3.635 17.79 4.103 C 17.925 4.456 18.091 4.984 18.135 5.96 C 18.183 7.016 18.194 7.329 18.194 10 C 18.194 12.671 18.183 12.984 18.135 14.04 C 18.091 15.016 17.929 15.544 17.79 15.897 C 17.607 16.365 17.393 16.698 17.044 17.048 C 16.694 17.397 16.361 17.615 15.893 17.794 C 15.54 17.929 15.012 18.095 14.036 18.139 C 12.98 18.186 12.667 18.198 9.996 18.198 C 7.325 18.198 7.012 18.186 5.956 18.139 C 4.98 18.095 4.452 17.933 4.099 17.794 C 3.631 17.611 3.298 17.397 2.948 17.048 C 2.599 16.698 2.381 16.365 2.202 15.897 C 2.067 15.544 1.901 15.016 1.857 14.04 C 1.81 12.984 1.798 12.671 1.798 10 C 1.798 7.329 1.81 7.016 1.857 5.96 C 1.901 4.984 2.063 4.456 2.202 4.103 C 2.385 3.635 2.599 3.302 2.948 2.952 C 3.298 2.603 3.631 2.385 4.099 2.206 C 4.452 2.071 4.98 1.905 5.956 1.861 C 7.012 1.81 7.329 1.802 9.996 1.802 Z" fill="rgb(0,0,0)" /> </g> </symbol></svg>JSON 格式数据结构预览
{ "name": "FigmaIcon", "description": "", "width": 24, "height": 24, "id": [ "3627:1307", "046c56ddfafa72e2cbb4b521de681feed7b9db0b" ], "paths": [ { "color": { "r": 0, "g": 0, "b": 0 }, "evenodd": false, "opacity": 1, "path": [ "M 3.9 0 L 9.9 0 C 12.053 0 13.8 1.747 13.8 3.9 C 13.8 5.106 13.252 6.185 12.391 6.9 C 13.252 7.616 13.8 8.694 13.8 9.9 C 13.8 12.053 12.053 13.8 9.9 13.8 C 9.127 13.8 8.406 13.575 7.8 13.186 L 7.8 15.9 C 7.8 18.053 6.053 19.8 3.9 19.8 C 1.747 19.8 0 18.053 0 15.9 C 0 14.694 0.548 13.616 1.409 12.9 C 0.548 12.184 0 11.106 0 9.9 C 0 8.694 0.548 7.616 1.409 6.9 C 0.548 6.185 0 5.106 0 3.9 C 0 1.747 1.747 0 3.9 0 Z M 1.8 3.9 C 1.8 2.741 2.741 1.8 3.9 1.8 L 6 1.8 L 6 6 L 3.9 6 C 2.741 6 1.8 5.059 1.8 3.9 Z M 6 9.902 C 6 9.901 6 9.901 6 9.9 C 6 9.899 6 9.899 6 9.898 L 6 7.8 L 3.9 7.8 C 2.741 7.8 1.8 8.741 1.8 9.9 C 1.8 11.059 2.741 12 3.9 12 L 6 12 L 6 9.902 Z M 7.8 9.9 C 7.8 11.059 8.741 12 9.9 12 C 11.059 12 12 11.059 12 9.9 C 12 8.741 11.059 7.8 9.9 7.8 C 8.741 7.8 7.8 8.741 7.8 9.9 Z M 9.9 6 C 11.059 6 12 5.059 12 3.9 C 12 2.741 11.059 1.8 9.9 1.8 L 7.8 1.8 L 7.8 6 L 9.9 6 Z M 1.8 15.9 C 1.8 14.741 2.741 13.8 3.9 13.8 L 6 13.8 L 6 15.9 C 6 17.059 5.059 18 3.9 18 C 2.741 18 1.8 17.059 1.8 15.9 Z" ], "x": 5.1, "y": 2.1 } ]}调研下来,JSON 虽易读,若要再编译成各端格式,往往仍要各端各用各的技术栈。前端的方案是直接消费 SVG Symbol,打包进 React 组件、用 Props 调图标;构建阶段再用 Node 工具把 Symbol 拆成适合移动端的单文件 SVG。整条链路可以交给 GitHub Actions 一类流程自动化,统一格式与交付也就闭环了。
版本管理
最后一个问题乍看棘手,其实前面几步已经埋好答案:用 GitHub 管代码即可。只是 GitHub 面向工程思维,我能逐行读 SVG 的 Diff,不是所有设计师都习惯这一套。
未来
接下来想把 VectorS 接到 GitHub:身份验证通过后,插件读取仓库里约定的 SVG Symbol,在界面里渲染出来,再通过 API 拉 Diff 做图形化对比——有点像 Abstract 在做的事。
我会尽量把这套流程带进以后的项目,也不排除把它做成自己的第一个小产品。回头看,以前对计算机图形只有模糊印象,这次折腾下来,总算又往清楚走近了一点。