乔布斯在《遗失的访谈》中说过「每个人都应该学习编程,因为它教会你如何思考」。
前言
在上一家公司,由于 UI 妹子离职和招聘问题,不得以兼任了 UI 视觉工作。虽然之前的产品/交互工作和 UI 有很多交集、自己的个人项目中也会有一些 UI 设计,但却不曾独自去设计一个完整产品的 UI。这一回,算是从「动口」到「动手」了,好在是 B 端产品,对视觉美观性要求没那么高,相对而言统一性更为重要。
之前作为 UI 上游环节时,我总是会给 UI 提意见,要求尽量统一、要求考虑实现成本……现在自己做 UI 后,才发现满足自己的要求挺困难的。虽然最终并没有达到我自己满意的程度,但这段实战经历,确实使我对 UI 设计的统一性有了一些更深入的理解,尤其是将「视觉设计的感性思维」和「程序实现的逻辑思维」融合后,有了一个全新的视角。
我的这些理解,对于很多前端工程师来说,可能属于「常识」;但对于没有前端经验的产品/交互/UI 设计师而言,我相信是有一定帮助的。如果我自己能早几年想明白,至少前几个项目在 UI 的统一性上可以提升不少。
P.S. 本文出现的代码都是 CSS 样式代码、没有任何复杂的逻辑。没有代码基础的同学,请不要抗拒,当成英文来读就行。
如何绘制一个界面?
从设计视角来看
-
在各类设计工具(如 Sketch)中,是图层的组合
从工程视角来看
无论从哪个角度,界面都是通过各种元素之间的各种组合、叠加,拼接而成。
如何使绘制出的界面统一?
既然界面是由各种元素拼出来的,那要达到整体界面统一,这些构成元素自然需要先统一。统一的方式,就包括建立一套「控件库」(或者说 Symbol、模板、Library、UI Kit……有许多不同的称呼),总之就是预定义一些「元素」及「元素组」,通过对有限的「元素」及「元素组」做统一,使得最终组合而成的界面也能在某种程度上实现统一。
如果你从事过 UI 相关工作,一定或多或少了解过诸如此类的「控件库」、或是 Atomic Design 之类的设计思想。
如何设计一套统一的「控件库」?
前面的都是概念,这才是实际要做的事情。为了省篇幅,我仅以「按钮」为例,讲述一下我对于「统一控件」的理解。
阶段一
这其实是我刚接触 CSS 时的理解,现在只是回顾一下最初那种浅显的想法。
虽然这种理解非常浅显,但不少软件连这一步都没做到。
最初我以为,统一是:
所有的中号按钮,高度都是 24px
所有的蓝色按钮,色值都是 #0000FF
……
总之,只要最终看效果上去统一就行,没有考虑其背后的实现,没有「控件库」的概念。
写成代码,可能会是这样:
- 假设有 99 个按钮,每个按钮都有自己的样式,只是这 99 种样式被强行设成了相同。
.button-1 {
height: 24px;
background-color: #0000FF;}
.button-2 {
height: 24px;
background-color: #0000FF;}
...
.button-99 {
height: 24px;
background-color: #0000FF;}
阶段二
这是我自己动手做 UI 之前的想法,要求 UI 设计师预先规划好所有可能会出现按钮(3 种尺寸、每种尺寸又有 3 种不同颜色,共计 9 种按钮)。
这时候,我以为统一是:
预先枚举出所有可能用到的标准控件,加入控件库。
程序实现时,统一使用控件库中的标准控件。
……
虽然有了「控件库」的概念,但实现手段是非常粗暴的「枚举法」
写成代码,可能会是这样:
- 假设有 99 个按钮,所有按钮都是只从这 9 种预设样式中选一种
.button-normal-small {
height: 20px;
background-color: #FFFFFF;}
.button-normal-middle{
height: 24px;
background-color: #FFFFFF;}
.button-normal-large {
height: 32px;
background-color: #FFFFFF;}
.button-blue-small {
height: 20px;
background-color: #0000FF;}
.button-blue-middle{
height: 24px;
background-color: #0000FF;}
.button-blue-large {
height: 32px;
background-color: #0000FF;}
.button-red-small {
height: 20px;
background-color: #FF0000;}
.button-red-middle{
height: 24px;
background-color: #FF0000;}
.button-red-large {
height: 32px;
background-color: #FF0000;}
阶段三
这时,我需要自己去制作控件库了。作为一个懒人、我可不想枚举出所有的按钮。所谓懒惰使人进步,我带着问题去看了一下各大网站的网页代码。很容易就能发现了规律,人家是用多个 css 类叠加出来的。
对嘛!设计的时候是 3 种尺寸、3 种颜色,也就是分「尺寸」和「颜色」这 2 个维度,那实现的时候也应该是用维度叠加,而不是枚举。
原来需要画 3 × 3 = 9 个按钮,现在只需要画 3 +3 = 6 个了!
这时候,我以为统一是:
将一个控件抽象成「特定维度的叠加组合」
实现时,通过限定这些维度来实现统一
写成代码,可能会是这样:
- 分别设「color、size」这 2 个维度的样式,使用按钮时,用这 2 个维度的叠加效果。
.button.color-normal {
background-color: #FFFFFF;}
.button.color-blue {
background-color: #0000FF;}
.button.color-red {
background-color: #FF0000;}
.button.size-small{
height: 20px;}
.button.size-middle{
height: 24px;}
.button.size-large{
height: 32px;}
阶段四
其实对于「设计指南」而言,通常就是只到「阶段三」的层次。强如 Google 的 Material Design,虽然标注很细致,但依然是属于「阶段三」这个层次。
基于 Material Design 的这种标注,你可以尝试解释一下这 2 个问题:
- 为什么按钮的高度是 36dp?
- 为什么这个按钮的最小宽度(min-width)是 64dp?
怎么样?这些具体的 dp 值是怎么得到的,能解释清楚吗?至少我做不到。
之前作为 UI 的上游环节,我没有深入去思考、停留在了这些表面的标注上。现在真正自己去设计一套控件库时,才发现,如果只看这些表面的细节,再详细都不够,还应当更进一步去思考得到这些结果的「推导过程」,否则只是在依葫芦画瓢、不是真正的理解。
回想到曾经看过的一个印象深刻的观点「优秀的设计,每一个像素都是有据可循的」
那么,什么叫做「有据可循」?
- 按我个人的理解,就是可以用准确的逻辑规则表述、而不是凭感觉
(其实感觉也是一种基于经验的规则,又名「神经网络算法」,只是我们自己无法理解、表述)
什么又是准确的逻辑规则?
- 代码!(伪代码也行)
顺着「用准确的代码解释每一个像素」这个思路,我又重新审视了一遍控件库。联想到之前做网站时扫过一眼的 scss(用变量定义 css 的一种语言),终于领悟到了「阶段四」:
控件的每一个属性都应该是有推导过程的「相对值」,而不是一个凭感觉直接得到具体结果的「绝对值」。
写成代码,可能会是这样:
- 所有「色值」、「像素值」都用「变量」表示。当然了,这不是真实的例子,只是为了做简单的示例
(关于「变量」的详细说明,请看下一节 Variables)
.button.color-normal {
background-color: $color-normal;}
.button.color-blue {
background-color: $color-blue;}
.button.color-red {
background-color: $color-red;}
.button.size-small{
height: $small;}
.button.size-middle{
height: $middle;}
.button.size-large{
height: $large;}
Variables(变量)
Variables,仅指 CSS 样式层面的变量。(这里我参考了 Blueprint 的命名,用了 Variables 一词,在其他很多设计系统(Design System)中,也都有类似概念。比如在 Saleforce Lightning 中,它被称之为 Tokens。)
等等,之前不是说「设计指南只到阶段三的层次」么?怎么突然很多设计系统中又有类似概念了?
还是因为代码。
- 在成熟的 Design System 中,除了设计指南部分,通常还有完整的开源代码
- 只是我之前看这些 Design System 时,未曾想到去看源代码部分
以我们自己的按钮举例,一层层拆解它的「变量」吧。(为了简化,暂不考虑按钮的 hover、down、disable、loading 等状态)。
先来看一下绝对值
这是我参照 Material Design 做的标注(单位都是 px)
对着这个按钮,再来看一下这 2 个问题:
- 为什么按钮的高度是 24px?
- 为什么这个按钮的最小宽度(min-width)是 54px?
回答这 2 个问题之前,还需要先进一步拆解一下「按钮」
按钮是什么?
- 在 Sketch 中,是「背景」上叠加「文字」
- 在 HTML&CSS 中,是「背景容器」内塞入「文字」。
既然是要「用准确的代码解释每一个像素」,我们得用代码的逻辑来看。CSS 盒模型 了解一下?
盒模型简单的说就是:
按钮的尺寸 = 容器外边距(margin) + 容器边框(border) + 容器内边距(padding) + 内部文字的尺寸
我们这个按钮对应的实际盒模型如下:
拆解问题一
参照上图的盒模型,可以先开始回答第一个问题
- 为什么按钮的高度是 24px?
解:
因为
- 外边距为 0;
- 边框为 1px,但有上下两条边,因此还要 ×2;
- 垂直方向的内边距为 2px,同样有上下两个边距,也要 ×2;
- 文字高度为 18px;
所以,将上述值相加即可得到
- 0 + (1px * 2) + (2px * 2) + 18px = 24px
继续追问……
文字的高度为什么是 18px?
解:
因为
- 使用了标准字号 12px
- 文字行高是字号的 1.5 倍,这点应该属于 UI 设计的常识吧?
所以,可以得到
- 12px * 1.5 = 18px
将标准字号命名为 $font-size-m,其对应的标准行高即为
- $line-height-m = $font-size-m * 1.5
继续追问……
为什么 border 是 1px? 垂直 padding 又是 2px?
解:
这 2 个问题,如果只看按钮本身,确实很难再解释为什么了
但它们也不是一个孤立存在的绝对值,背后也是有特定含义的变量1px 的 border,可以称之为「细边框」,即 $border-width-thin: 1px;
所有需要「细边框」的地方都用 $border-width-thin
类似的,2px 的 padding,可以称之为「超小间距」,即 $spacing-xs: 2px;
所有需要「超小间距」的情况都用 $spacing-xs
最终用「代码公式」来回答这问题,就是
24px = ($border-width-thin * 2) + ($spacing-xs * 2) + $line-height-m
拆解问题二
再来看一下问题二
为什么这个按钮的最小宽度(min-width)是 54px?
解题思路和「问题一」类似,这里只解释一下关键点:
因为我是按照最少 3 个汉字的宽度来设计的最小尺寸
- 之前提到过,标准字号命名为 $font-size-m,是 12px
- 换算一下,12px * 3 = 36px
另外水平方向的 padding 设了 8px,再加上两边各 1px 的边框
- 36px + 8px * 2 + 1px * 2 = 54px
所以,最终用「代码公式」来回答这问题,就是
- 54px = $font-size-m * 3 + ($spacing-xs * 2) + ($border-width-thin * 2)
「尺寸」总结
回顾一下几个涉及「尺寸」的属性,及其对应的变量值,在代码中如下:
(还有 border-radius 也是尺寸相关的属性,只是不影响宽/高计算,因此未曾提及。)
min-width: $font-size-m * 3; //最小宽度
padding: $spacing-xs $spacing-m; //内边距(前者为垂直方向、后者为水平方向)
font-size: $font-size-m; //字号
line-height: $line-height-m; //行高
border-width: $border-width-thin; //边框粗细
至于 $spacing-xs、$spacing-m、$font-size-m、等等,这些「变量」的值具体应该设多少,这里暂不深入。本文的主要还想表达「所有值都用有据可循的变量表示」这种思路,至于每一个变量的具体值设定,那是另一个庞大的话题了。
以下链接是我自己的设定方式,如有兴趣可供参考
https://github.com/UXplayer/GS_Guideline/blob/master/_sass/variables.scss
回到「阶段四」的例子
还记得之前「阶段四」的示例代码吗?是类似这样不真实的、极其简单的例子
.button.size-small{
height: $small;}
如果按照我们实际用的按钮来看,按钮「尺寸」维度应该如下:
- 对于不同尺寸的按钮,其「最小宽度、内边距、字号、行高」都一同变化
.size-small{
min-width: $font-size-s * 3;
padding: 0 $spacing-s;
font-size: $font-size-s;
line-height: $line-height-s;
}
.size-normal{
min-width: $font-size-m * 3;
padding: $spacing-xs $spacing-m;
font-size: $font-size-m;
line-height: $line-height-m;
}
.size-large{
min-width: $font-size-l * 3;
padding: $spacing-s $spacing-l;
font-size: $font-size-l;
line-height: $line-height-l;
}
颜色
对于「颜色」维度,就是背景色、文字色、边框色,这些包含色值的属性。
在颜色的使用上,UI 设计师通常都还是会预定义好色板的。即便没有用精确的代码来解释,如果有用 Sketch 的 Symbol,也已足够保证统一性。此外,颜色在代码实现上,通常也还是会枚举出色板中的颜色作为「变量」,除非你是做开源库,需要有一些色板的生成算法,否则不会尺寸那样包含各种计算规则。
因此「颜色」部分就不再赘述了,思路和「尺寸」部分是一样的,甚至更简单一些。
总结
啰里啰嗦地写了那么多,其实主旨还是那一点:
控件的每一个属性都应该是有推导过程的「相对值」,而不是一个凭感觉直接得到具体结果的「绝对值」。
一个完整的控件库,要比按钮复杂的多,但万变不离其宗,无非是「推导过程」更长一些罢了。
像程序学习,用工程思维、逻辑思维去重新审视你自以为熟悉的 UI 设计,也许还会发现更多的新大陆。
最后
我们的项目是用 QT 开发的 PC 端产品,我在给开发提需求的同时,用 HTML&CSS 写了一部分控件,可以点此查看 在线演示项目 (做了一些调整,和实际项目并不完全相同)
该演示项目的源代码可以在 GitHub 查看。因为我这个只是简单的 Demo 演示,比起大型开源项目的源码,读起来应该容易不少。
我为什么要自己用代码写一遍?
首先,批量调整样式时,代码比 Sketch 快得多;
其次,便于和开发沟通(给开发直接看代码,要比用自然语言解释规则轻松多了)