Abstract
最近尝试做跨端的应用,目标是:只需要写一套代码,通过不同的打包配置,各个端都能稳定运行。之前并没有做过适配手机移动端的工作,这次发现需要考虑的细节还是蛮多的,在此做记录。
从原生CSS到TailwindCSS
对于样式的处理,我经历了好几个阶段,也对其中的琐碎、麻烦、相互干扰、不够工程化深恶痛绝,可以说这玩意就是我从后端开发转向全栈的一个拦路虎。好在我现在踩了很多坑,也算积累了一点经验了。
原生CSS
大家应该都知道最初的“三剑客”,也就是Html、JavaScript、CSS。如果只要做一个简单的demo,也就是单网页,我们可以这么做:
- 在
html
标签上加上CSS类名,比如.my-container
- 在CSS文件中,写CSS样式,例如:
- 在HTML中使用
stylesheet
标签引入CSS文件。
这也就是最初始的写法,也是我们在网页上F12,去浏览别人的网页看到的样子。这么写有一些缺点:
-
对于命名能力要求高,心智负担重,往往需要再学习一套命名的规范。 写函数容易,给函数命名难,所以有了匿名函数,而样式也是一样:写一个标题,CSS类名叫
heading
, 再写一个标题呢?写一个描述,叫description
, 再写一个呢?如果有各种不同样式布局的标题、容器、文本,逐渐就变得越来越搞不清楚哪个对哪个。如果是多人合作的项目,还需要沟通好规范。 -
样式、模板位置分离,难改。我们现在需要改某个页面元素的样式,需要:
①找到HTML元素的类名(可能有多个应用优先级不同的样式,所以需要判断是哪个决定了它最终呈现出的样式) ②跳到CSS文件中,修改样式 ③查看网页上样式有没有修改成功 ④检查其他页面使用相同类名的地方是否正常
在多个文件和窗口中跳来跳去,很容易打断思路,也会越来越难以维护。 3. CSS原生写法不支持嵌套,所有伪类(::before, ::after) 样式、状态样式(:hover, :focus)等,都需要独立闭合的大括号括起来,写起来相当麻烦。
- 样式是否真的生效相当难以判断,很多时候都是看页面上样式是不是正常了。这种非强制要求可能导致”技术债“越积越深,直到有一天要求调整样式的时候,发现已经极为混乱和难以维护。
工程化、框架和PostCSS
在原生Html和Javascript有了框架:Vue、React等等后,CSS也有了更好的处理方式。
-
SCSS
/LESS
等CSS后处理器出现,支持嵌套等各种语法糖,让CSS写起来更为简便。 -
支持组件内局部应用的样式。例如:Vue的 标签支持加上
scoped
关键字,让这个组件内的样式只在本组件内有效,避免样式污染,也有效降低了心智负担。
原理:通过打包时的插件配置,将开发时所写的样式还原成浏览器可以识别的CSS文件,将嵌套的扁平化,局部或者不同适用场景的统一添加前缀。
TailwindCSS: 快速入门+设计思想
一旦接触了TailwindCSS
很难不被它的直接简洁迷上。
它相当于提供了一整套速写词,再也不用去记臃肿的写法了!再也不用三四行来写一个属性了!也不用再到处找样式文件,只需要在html
标签里写上一串原子类的className
,样式就直接显示到了屏幕上。
举例
原先设置字号需要设置
13px
、14px
等,现在只需要写text-xs 或者text-sm。原先需要设置圆角尺寸,都是需要传入特定的值,现在只需要传入rounded-md
、rounded-lg
。 阴影原先需要自己写,现在只需要写shadow-md
和shadow-sm
。 换言之,只需要给出语义化的类名,就可以实现想要的效果,再也不需要关心琐碎的细节。
💡 快速的可见即可得
从html元素的className中读到
bg-red
,直接就能看到页面元素的背景是红色的。而原来则需要多一次跳转:先看到类名,再找到样式实现。当整个系统都如此,对于降低心智负担是极为重要的。
最棒的就是它内置了一套设计系统,包括尺寸、状态和色彩系统。
1. 尺寸
直接使用符合响应式标准的单位rem
,和一套语义化的封装: sm
、 md
、lg
……换言之,我们不需要再考虑这个按钮到底应该占多大,而是应该从它实际的功能和使用场景出发,考虑它应该是大、中等还是小。
2. 色彩
内置了多种标准颜色的色彩系统,也支持自己定义色调梯度,使用颜色+渐进数值来提供原始色值和类名的对应关系,更符合直觉:需要颜色更深一些,数字就更大,需要颜色更小一些,数字就更浅。
举例:主要按钮使用 bg-blue-600
, 背景色使用 bg-blue-100
,而不需要再去调色盘查看哪个是主要的蓝色,哪个是浅蓝色。透明度也不需要自己写了,而是直接使用形如bg-blue-600/80
的格式,就是透明度80%的意思。
3. 状态
在写了一套样式后,我们需要考虑它处于不同上下文的种种状态,例如:在不同屏幕尺寸下/明暗或者不同主题下/用户不同行为下/数据不同状态下等,我将其统一称之为“状态”。
举例: ① 在电脑上可以排列成每行四个的卡片,在手机屏幕上只能放得下每行一个,不然内容放不下。间距也应该随之缩小。
② 按钮在悬停时应该有特殊的样式,给用户以“我正在交互热区”的正反馈,当按钮不能点击时,应该有disabled
的状态。当正在从接口请求数据时,页面和按钮应该是loading
状态。
③ 在黑暗模式下,所有的色彩都需要调整成适配的样子。例如原本是白底黑字,暗黑模式就应该是黑底白字。
TailwindCSS支持使用冒号来响应状态查询、容器查询和媒体查询。例如:bg-red-500 hover:bg-red-600
,意思就是在hover状态下背景颜色加深。
可读性非常强。对于响应式的尺寸设计,也可以通过md:
、 sm:
的格式来实现。
提供了一整套内置的设计系统外,也支持传入特定值来支持特殊的样式,既保留了系统的优雅简洁,也保留了开放和灵活性。目前很多UI框架都使用TailwindCSS实现。
大量使用了TailwindCSS编程后,也会觉得抽象粒度不够,写起来费劲。我们终究是需要抽象出来一套标准的UI组件复用在系统中。那么怎么去设计这一套UI组件呢?
Radix UI Themes:完善的样式系统都有哪些东西
在研究样式系统时,我看到了
Radix UI , 精致漂亮色彩丰富,完完全全是我喜欢的样子。阅读它Theme
的源码,让我体悟到了样式系统的设计原则。
语义化:Design Token的运用
我需要一个创建的按钮,希望它足够醒目、重要。它是蓝色的(蓝色是我们的主色调!)
别再写background-color: blue
了!
对,用tailwindcss写起来简洁一些……bg-blue-600
……不,也别这么写了!
应该写的是: bg-accent-6,意思是第六梯度的主色调,这样主色调变了,也不需要去改动这一部分的代码。
这就是我们应该做的:在写样式时,我们写语义化的语句(给它重要的颜色,重要的字重,重要的阴影,hover时颜色变深),然后再通过给变量赋值的方式,来使其真正生效(重要的颜色是什么颜色?hover时具体颜色变深多少?),这样我们只需要写一次,就可以实现各种不同的主题。
业务层逻辑的剥离:样式系统只负责样式
对于一个重要的按钮,我们写出来的可能是这样:
bg-accent-6 hover:bg-accent-7 shadow-md text-md
……
把这些封装在工具类中,最终给这个重要的按钮的类名就是 ——
btn-primary
?
不。这样就是样式系统与业务逻辑再一次重叠和混淆了。primary 的意思是主要的,包含着一种业务逻辑层面的判断。正确的命名应该是只说明样式,这样上层调用时不会造成任何歧义和混淆。
Radix UI的做法是给出了六种变体(variant):classic
、solid
、 soft
、surface
、 ghost
、 outline
。 在使用的时候,当然可以根据业务逻辑去判断出,classic
和solid
一般用于一级按钮,soft
和surface
用于二级按钮……但那是业务层关心的事。
足够低的优先度,给扩展提供的可能性
Radix Theme中所提供出的所有组件,都支持外部传入的样式进行覆盖和改写,保留了足够的灵活度。我们尽可以继续使用TailwindCSS来加上自己喜欢的样式。
总结
样式系统必不可少!
将复杂琐碎的样式细节隐藏起来,写业务代码时只需要关注交互逻辑、数据流向等,而无需再关注样式……这也就是样式系统的意义所在。
而对于跨端开发的交互方面的抽象,又是另一个问题了……等我踩完坑,再写一篇。