去年很火的 Tailwind CSS 是何方神圣,到底是 Atomic CSS 余孽的卷土重来还是真的有点东西。Tailwind CSS 如何帮助我们建立界面样式到设计语言的连接,Utility-first 的 CSS 工作流是怎样的,以及,如何基于 Tailwind CSS 为自己的团队定制一套舒服的 CSS 框架。
我们太有限了,我们只能做我们觉得是对的事情,然后接受它的事与愿违。– 罗翔
CSS 工程化要解决的问题
至少在中后台研发领域,我觉得团队在 CSS 领域会遇到以下几个问题要解决:
强制一致性:如何强制规约界面的字体、字号和颜色收敛。
设计关联:如何形成界面样式到设计语言的连接与对应。
语义化:关注点分离是银弹还是误解。
内联样式:在何时进行抽象。
强制一致性
在解释什么是强制一致性之前,请大家来猜一下,在 yuque.com 上,有多少种不同的字号、文字颜色和背景颜色。
答案可能跟你预期的有些差异。yuque.com 上一共有 34 种不同的字号、77 种不同的文字颜色和 56 种不同的背景颜色。https://cssstats.com/stats/?url=https://yuque.com/
事实上 yuque.com 已经做得足够好了。因为在 Github.com 上一共有 56 种字号,163 种文字颜色和 147 种背景颜色。在一些企业级的 Web 应用上其实会更可怕,例如 Gitlab 一共有 59 种字号、402 种文字颜色和 239 种背景颜色。
为什么会出现这种事情?当设计师把设计稿交给我们之后,还原设计稿的最便捷方式之一就是使用设计工具的 “Copy as CSS” 功能导出对应的 CSS,看起来不错。
/* Lorem ipsum dolor si */
position: absolute;
width: 232px;
height: 144px;
font-family: Roboto;
font-style: normal;
font-weight: normal;
font-size: 16px;
line-height: 24px;
/* or 150% */
color: rgba(0, 0, 0, 0.541327);
观察这段 CSS,会发现,在这里,字体、字号和颜色都是一个自由的,没有规约的值。每一行 CSS 都是一个空白的画布,没有人能阻止你使用任何你想要的值。这就是为什么同样是视觉设计师想要的语雀品牌绿,在 CSS 中至少有六种写法,并且以下三种我基本看不出有啥区别……
所以这里所谓的 强制一致性指的是开发人员在书写 CSS 的过程中,属性的值应该总是从一个有限的集合中去取。而不是任意取值。
Design Token
再来。这是一份 Google Materia Design 的设计稿。会发现 Google 的设计师除了标注了这些元素的颜色值之外,还贴心的写了个名字 Gray 900。这就是所谓的 Design Token:
Design tokens are all the values needed to construct and maintain a design system — spacing, color, typography, object styles, animation, etc. — represented as data.
一份优秀的设计稿
在一份设计规范中,设计师首先会决定使用一些值。然后给它们设置一个上下文无关的名字,即 Global Token 。用于让其他的 token 引用。
在此之上,在特定的上下文和抽象中,会基于 Global Token 生成一个具名的 Alias Token,用于传达 Global Token 的设计预期。
最后,决定某个组件要使用某个特定 Design Token 时,会创建一个 Component-specific Token,让开发人员能给予 Alias Token 去定义组件的 Token 别名。
从色值到组件
下面来个例子。
普通开发 | // css button { background: 2680EB; } | 裸写色值。无法做到强制一致性 |
文艺开发 | // css :root { –blue400: 2680EB; –ctaBackgroundColor: var(–blue400); } button { background: var(–ctaBackgroundColor); } | 通过 css variable 把 Design Token 代码化。 使用的时候引用。不要裸写色值。 这里并没有再创建一遍 Component-specific Token |
XX开发 | // tailwind.config.js { theme: { extend: { colors: { blue: { 400: 2680EB } } } } } // css @layer components { .cta-background-color { @apply bg-blue-400; } .button-cta-background-color { @extend .cta-background-color; } } // html Click Me | 通过 tailwind.config.js 把 Design Token 与值做关注点分离 通过 tailwind 的 layer directive 把 Design Token 与 Alias token 做连接 创建出 Component-specific Token,通过类名暴露给 HTML 组件使用 |
语义化和关注点分离
看到这里可能有小伙伴开始懵逼了,是不是有哪里搞错了,不是说好了 Tailwind CSS 就是当初被喷成狗的 Atomic CSS 换了个皮卷土重来么,怎么跟你上面讲的不太一样?甚至官网上的示例都是这样一串 class,是不是你在过度解读 Tailwind CSS,夹带了私货?
前面已经讨论了如何把样式和设计通过 Design Token 连接起来。但接下来可能要讨论一些比较奇怪的东西。
用雨燕首页的 最近常访问的应用 列表为例。按照古典时代关注点分离的最佳实践,也就是传说中的 写 HTML 的时候不用关心样式,我们会怎么写这样的列表:
class
="application-list"><li>
<ahref="/yuyan/yuyanAssets">
<imgsrc="https://gw.alipayobjects.com/zos/basement_prod/9a7a9c64-01ee-45ca-a615-6063a24f70a9.svg"/>
<div>
<h4>yuyanAssetsh4>
<span>雨燕前端应用span>
div>
a>
li>
ul>
.application-list{
list-style:none;
>li {
background:fff;
>a {
display:block;
padding:18px 22px;
>img {
display:block;
width:38px;
height:38px;
float:left;
}
>div {
display:inline-block;
>h4 {
color:314659;
font-weight:600;
margin:0;
}
>span {
color:697b8c;
font-size:12px;
}
}
}
}
}
现在谁这么写 CSS 绝对可能会被揍。它最大的坏处是 HTML 和 CSS 的层次结构必须完全对应。HTML 怎么嵌套的, CSS 就必须怎么嵌套。
后来我们开始有了 BEM,写出来的 HTML 会不那么欠揍了:
class
="application-list"><liclass="application-list__item">
<aclass="application-list__link"href="/yuyan/yuyanAssets">
<imgclass="application-list__img"src="https://gw.alipayobjects.com/zos/basement_prod/9a7a9c64-01ee-45ca-a615-6063a24f70a9.svg"/>
<divclass="application-list__content">
<h4class="application-list__title">yuyanAssetsh4>
<spanclass="application-list__description">雨燕前端应用span>
div>
a>
li>
ul>
.application-list{
list-style:none;
&__item{
background:fff;
}
&__link{
display:block;
padding:18px 22px;
}
&__img{
display:block;
width:38px;
height:38px;
float:left;
}
&__content{
display:inline-block;
}
&__title{
color:314659;
font-weight:600;
margin:0;
}
&__description{
color:697b8c;
font-size:12px;
}
}
现在看起来舒服多了。但是问题来了。如何复用?
例如现在需要写一个结构非常类似的列表,例如雨燕首页的进行中的迭代的列表,希望最大限度复用上面这个结构。一种不纠结的做法是拷一遍。另一种做法是使用注入 less / sass 的 mixin 或者 extends 功能复用样式。
复用:使用 mixin 或者 extends
class
="application-list"><liclass="application-list__item">
<aclass="application-list__link"href="/yuyan/yuyanAssets">
<imgclass="application-list__img"src="https://gw.alipayobjects.com/zos/basement_prod/9a7a9c64-01ee-45ca-a615-6063a24f70a9.svg"/>
<divclass="application-list__content">
<h4class="application-list__title">yuyanAssetsh4>
<spanclass="application-list__description">雨燕前端应用span>
div>
a>
li>
ul>
class
="sprint-list"><liclass="sprint-list__item">
<aclass="sprint-list__link"href="/yuyan/yuyanAssets">
<imgclass="sprint-list__img"src="https://gw.alipayobjects.com/zos/rmsportal/yeSGzTolyopHKmBeKQHC.svg"/>
<divclass="sprint-list__content">
<h4class="sprint-list__title">迭代1h4>
<spanclass="sprint-list__description">basement/basementwebspan>
div>
a>
li>
ul>
.application-list{
list-style:none;
&__item{
background:fff;
}
&__link{
display:block;
padding:18px 22px;
}
&__img{
display:block;
width:38px;
height:38px;
float:left;
}
&__content{
display:inline-block;
}
&__title{
color:314659;
font-weight:600;
margin:0;
}
&__description{
color:697b8c;
font-size:12px;
}
}
.sprint-list{
.application-list;
}
复用:创建内容无关样式
另一种方案是创建一个内容无关的 CSS,由 application 和 sprint 两个实体列表共同使用。如果需要只修改 sprint 列表中的样式,又不想影响到其他 entiry-list,就需要语义化的增加一个 class,然后通过这个新的语义化 class 来覆盖样式。
class
="entity-list"><liclass="entity-list__item">
<aclass="entity-list__link"href="/yuyan/yuyanAssets">
<imgclass="entity-list__img"src="https://gw.alipayobjects.com/zos/basement_prod/9a7a9c64-01ee-45ca-a615-6063a24f70a9.svg"/>
<divclass="entity-list__content">
<h4class="entity-list__title">yuyanAssetsh4>
<spanclass="entity-list__description">雨燕前端应用span>
div>
a>
li>
ul>
class
="entity-list sprint"><liclass="entity-list__item">
<aclass="entity-list__link"href="/yuyan/yuyanAssets">
<imgclass="entity-list__img"src="https://gw.alipayobjects.com/zos/rmsportal/yeSGzTolyopHKmBeKQHC.svg"/>
<divclass="entity-list__content">
<h4class="entity-list__title">迭代1h4>
<spanclass="entity-list__description">basement/basementwebspan>
div>
a>
li>
ul>
.entity-list{
list-style:none;
&__item{
background:fff;
}
&__link{
display:block;
padding:18px 22px;
}
&__img{
display:block;
width:38px;
height:38px;
float:left;
}
&__content{
display:inline-block;
}
&__title{
color:314659;
font-weight:600;
margin:0;
}
&__description{
color:697b8c;
font-size:12px;
}
}
.entity-list.sprint{
.entity-list__img{
margin-right:8px;
}
}
这只是一个选择……
要么保持关注度分离,在写 HTML 的时候(尽量)不关心 CSS,使用 mixin 和 extends 做复用。
要么开始尝试创建内容无关的样式,并以可复用的方式命名所有内容,这就是 Tailwind CSS 作者的理念。
内联样式
if(status ===FAIL) {
return
F5222D , fontSize: 16,float:right}} />;}
不知道大家怎么看这样的代码。这是一个 Icon,在这个场景下我们需要去给它设置颜色和字号。这样写内联样式总觉得很奇怪,其实也合理。因为如果我们真的为了这个场景去创建个样式出来,就真的太奇怪了。并且会带来额外的起名负担。还会担心重名(于是我们又引入了 css module),所以很可能你会写出来这样一个 class:
// JSX:
if(status ===FAIL) {
return
"redCloseIconRight" />;}
// CSS
.redCloseIconAlignRight {
color:F5222D;
fontSize:16px;
float: right;
}
内联样式会带来两个问题:
无法做到强制一致性。除非你要在内联样式里写 CSS Variable,否则没办法保证样式值的收敛。
过于复杂的内联样式很恶心,例如 box-shadow、font-family。很容易又转回到创建一个局部 class 的情形。
在这两种情况下,为一些常用的样式设定 Utility Classes 其实非常方便。.clearfix
就是特别典型的例子。Tailwind CSS 的另一个爽点就在这里。通过配置,可以创建出链接到 Design Token 的 Utility Classes。不管在 css 里通过 apply 复用,还是直接在 jsx 里用,都非常方便:
// JSX:
if(status ===FAIL) {
return
"text-red-500 text-base" />;}
// 加个 shadow 也很方便:
if(status ===FAIL) {
return
"text-red-500 text-base shadow-sm" />;}
正式介绍一下 Tailwind CSS
写到这里终于可以正式介绍一下 Tailwind CSS 了。
Q: Tailwind CSS 是 Atomic CSS 吗?
A: 不是。它是一个 Utility First 的 CSS 框架。提供了对提升 CSS 开发效率的一系列 Utility Class 的抽象,以及自定义 Utility Class 的方法。
Q: 然后呢
A: 以 tailwind.config.js 为桥梁,建立起属于自己团队的从 Design System 到 CSS 框架的连接。
Q: 那如何低成本解决原先有个 class 叫.black
,然后很多组件都用了,但是突然有需求要把他们改成蓝色
的问题
A: 按照上面 Design Token 的做法,做component-layer
封装即可。
如何做?
以 yuyanAssets 为例子:
1. 在 tailwind.config.js 中定义 Design Token
module.exports={
darkMode:false, // or media or class
purge:[
./src/**/*.{js,jsx,ts,tsx}
],
theme:{
extend:{
fontFamily:{
mono:[ Menlo, Consolas, monaco, monospace ],
},
fontSize:{
xs:12px,
sm:14px,
base:16px,
lg:20px,
xl:24px,
},
fontWeight:{
light:300,
normal:400,
medium:500,
},
colors:{
primary:1890ff,
info:2c92f6,
warn:ffbf00,
success:00a854,
fail:f04134,
doing:697b8c,
pause:a3b1bf,
enable:52c41a,
disable:f5222d,
danger:f04135,
icon:{
0:f04134,
1:00a854,
2:108ee9,
3:f5317f,
4:f56a00,
5:7265e6,
6:ffbf00,
7:00a2ae,
}
},
boxShadow:{
DEFAULT:0px 4px 4px rgba(0, 55, 107, 0.04),
},
},
},
variants:{
extend:{},
},
plugins:[],
}
2. 把原先 less 中散落的各种 Design Token 使用 apply 描述。
.panel-body {
flex:1;
background:@background-color-content;
border-radius:@border-radius-default;
box-shadow:@shadow-default;
overflow: hidden;
}
改成
@layercomponents {
.panel-background {
@applygb-white;
}
}
.panel-body {
.panel-background;
@applyflex-1rounded shadow;
overflow: hidden;
}
3. 去除无用抽象。把内联样式改写成 Utility Class
<divstyle={{width:120,marginLeft:16,marginRight:12}}>
<Progresspercent={progress}format={percent=>`${percent}%`} />
div>
<AvatarclassName={`icon-producticon-color-${colorIndex}`}>
{iconLetter}
Avatar>
// 改成
<divclassName="w-28 ml-4 mr-3">
<Progresspercent={progress}format={percent=>`${percent}%`} />
div>
<AvatarclassName={`icon-productbg-color-${colorIndex}`}>
{iconLetter}
Avatar>
多余的话
在 2021 年的当下,一个前端工程师在工作中,花在 JavaScript、CSS 和 Html 的上的时间占比大概跟前面的排序一样。JavaScript > CSS >>> Html。早年间前端工程师可能还会通过模板关注到 Html 的结构,而现在,随着 React 接管了 DOM,前端工程师的关注点已经慢慢从 HTML 移动到了 JSX 上。
甚至在整个生产过程也跟古典的写语义化的 HTML -> 给他们取个 Class -> 写选择器 -> 写 CSS 不同了。工程师总是尝试优先使用已经写好的组件(如果没有就写一个),然后组合搭建出整个界面。甚至在布局的时候都很少关注 HTML:比如 antd 已经提供了 Layout 布局组件,又比如 Material Design 整个布局都是基于 Responsive Layout 的,基本上没有考虑有关 HTML 文档流的什么事情。
在 React 刚出来的时候,有很大一部分前端工程师表示 JSX 这种把逻辑和模板混在一起写的方式就是倒退。但随着 Flutter 和 Swift UI 的流行,大家惊奇的发现整个业界都在倒退。
所以也许我们可以换个想法,把 HTML 和 CSS 当成 UI 框架输出的结果。在书写代码的过程中,它们是什么样子的,可能并没有那么重要。
structContentView:View{
varbody: someView{
VStack{
Text("Turtle Rock")
.font(.title)
Text("Joshua Tree National Park")
.font(.subheadline)
}
}
}
@override
Widgetbuild(BuildContext context) {
returnGestureDetector(
onTap:() {
controller
..reset()
..forward();
},
child:RotationTransition(
turns:animation,
child:Stack(
children:[
Positioned.fill(
child:FlutterLogo(),
),
Center(
child:Text(
Clickme!,
style:TextStyle(
fontSize:60.0,
fontWeight:FontWeight.bold,
),
),
),
],
),
),
);
引用
https://spectrum.adobe.com/page/design-tokens/
https://adamwathan.me/css-utility-classes-and-separation-of-concerns/
https://css-tricks.com/bem-101/
关注「Alibaba F2E」微信公众号(左)微信视频号(右)
把握阿里巴巴前端新动向
声明:本文部分素材转载自互联网,如有侵权立即删除 。
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!
8. 精力有限,不少源码未能详细测试(解密),不能分辨部分源码是病毒还是误报,所以没有进行任何修改,大家使用前请进行甄别
丞旭猿论坛
暂无评论内容