原子css落地经验分享 | 让我的CSS 体积减少80%?
目前Facebook、Twitter已经使用原子CSS重构了他们的项目,新的CSS编写和构建方式让Facebook的主页减少了80%的CSS体积,提高了视觉还原效率。我们团队项目中也落地了原子css,来讲述一下我们遇到的问题,分享我们落地的经验。
本文主要结构:
- 原子CSS的历史与特性介绍
- 前端在B端实践中的一些经验
- 实践前的疑虑
- 展望与设想
鉴于作者的局限性,文中可能会出现一些纰漏,敬请纠正。
原子化特性基本介绍与原理
这一章节主要介绍当代原子化CSS的特性,特性背景,对已经了解的人来说篇幅会过长,可以跳过。
所以省流后就是:
- 按需构建
- JIT(即时编译)
- 主题/响应式支持
文艺复兴
在过去,我们会通过less/scss loop来生成一些重复功能性的css:
.loop(@cursor) when (@cursor > 0) {
.loop((@cursor - 1));
.m-@{cursor}{
margin: (1px * @cursor);
}
}
.loop();
这样我就生产了50个不同px的margin,为了方便,也给padding的left,top等都遍历一次,在当时刀耕火种的时代,也算是提升研效的大杀器。
但这样也会产生一个问题,就是产生过多冗余的样式,css的利用率不高。
现在工程化的提升,这样的方式就大可不必了。
目前原子化css的原理就是在限定范围的文件内进行class正则匹配扫描,可以看下面的白名单匹配器。所以,文件中用到哪个class name就打包哪个。
{
purge: {
options: {
whitelistPatterns: [
/^bg-.*/,
/^font-.*/
]
}
}
/* ... */
}
这个就是原子css一大特性按需构建,解决了样式冗余导致的打包体积过大的问题。
所以,理念和几年前是一样的,只是现在有工程化的加持,可以更好解决原有原子化的缺陷。
JIT(即时编译)
在上面的less loop中,我们穷举了1-50的margin,可是我需要一个margin:72px怎么办?继续loop不太合理,因为我们没法穷举所有的单位,因此,我们可以根据marign后面的单位进行即时编译。
比如m-72px最后会被编译成:
margin:72px;
颜色也可以编译:
// 匹配
text-{color} -> color: rgba(...);
border-hex-{hex} -> border-color: rgba(...);
// class语法
.text-cyan-400
.border-hex-6dd1c7
// 生成
.border-hex-6dd1c7 {
--tw-border-opacity: 1;
border-color: rgba(109, 209, 199, var(--tw-border-opacity));
}
.text-cyan-400 {
--tw-text-opacity: 1;
color: rgba(34, 211, 238, var(--tw-text-opacity));
}
主题/响应式
这部分我们实践的比较少,所以不做过深讨论。
其他特性大家可以自己到官网了解,这里不做赘述:
有谁在用
国外较大的实践比较多,国内确实比较少,腾讯部分团队也在使用,可以搜索一下关键字。
主要是说明这玩意儿可以放心用:
facebook

twitter

积累的经验
我们使用的是windicss,所以实践经验都基于windicss的特性,其他CSS原子化框架都大同小异。
与设计规范的结合
从方言到普通话。
在我看来,tailwind不仅仅是一个utils css库,而是一套业界原子css的命名规范,这使得后来者windicss,unocss的命名都基本按照了tailwind的规范。
当前,设计侧也很注重组件和原子,比如sketch的symbol,figma的token。属性和布局也都有相对应的规范。
下面就讲设计的规范翻译成class的成果。我们在项目中就可以直接shadow来表示规范中的"shadow"。

布局
布局简直太舒适了,当我记住了这些词后,写页面的效率起码提升50%(对我而言,感觉效率可能还会再高一些):
- 不用切换css上下文
- 不用给组件起名字
- 不用考虑css的scope产生的副作用
注意space的使用

playground
子元素的重复样式
子元素重复的class可以通过伪类写在父元素上,这样更加整洁,也减少体积。
同样的布局实现,也可以采用下面这种方式:

playground
伪类与组合
伪类可以通过组合的方式一起触发,基本覆盖了我们常用的场景:

playground
safelist
因为windicss的原理还是正则匹配,只能做静态扫描,无法利用js运行结果。因此classname不能做拆分写。
这样使用会导致样式不生效:
<div className={`mt-[${size === 'lg' ? '22px' : '17px' }]`}></div>
下面是正确的使用方式:
<div className={ size === 'lg' ? 'mt-[22px]' : 'mt-[17px]' }></div>
可以有2种方式解决这个问题:
- 放在js变量中
const CURRENCY_COLOR_MAP = {
[CurrencyType.USD]: 'bg-[#003491]',
[CurrencyType.CAD]: 'bg-[#D3073C]',
[CurrencyType.GBP]: 'bg-[#16367D]',
[CurrencyType.EUR]: 'bg-[#1B9077]',
[CurrencyType.JPY]: 'bg-[#E8374F]',
[CurrencyType.NZD]: 'bg-[#2778B9]',
[CurrencyType.AUD]: 'bg-[#215ABF]',
};
// ⚠️注意,因为这几个颜色只用到一次,且不在规范当中,如果是设计规范的最好按规范来比如 bg-red-1
这样扫描的时候就能加入构建。
- 使用safelist
export default defineConfig({
safelist: 'p-1 p-2 p-3 p-4 bg-[#003491]',
})
这样,windicss会提前把这些样式加入构建,无论你在哪里使用,或者拆得更细。
组合的两种方式
拥有了原子化,你仍然可以写less/css,解决原子化不擅长的地方:
- 在style中

- 在配置文件中
export default {
theme: {
/* ... */
},
shortcuts: {
'btn': 'py-2 px-4 font-semibold rounded-lg shadow-md',
'btn-green': 'text-white bg-green-500 hover:bg-green-700',
},
}
杂项
绘制比例矩形
// 正方形
aspect-square
// 16:9
aspect-video
https://windicss.org/plugins/official/aspect-ratio.html
文本溢出处理
// 一行超过就....
line-clamp-1
// 两行超过就....
line-clamp-2
https://windicss.org/plugins/official/line-clamp.html
鼠标箭头

export default {
theme: {
cursor: {
'auto': 'auto',
'default': 'default',
'pointer': 'pointer',
'wait': 'wait',
'text': 'text',
'move': 'move',
'not-allowed': 'not-allowed',
'crosshair': 'crosshair',
'zoom-in': 'zoom-in',
},
},
}
你可能担心的问题
除了这些,你可能仍然还有疑虑,也是我之前遇到的一些问题,不过都找到一些初步解决方案。
如果你还有其他的疑虑,可以评论,我们一起思考解决。
css体积转嫁到html的问题
下面的图是使用原子化css和不使用的体积对比图,很明显,原子css大小是有极限的,其最大值就是所有内置属性的大小,但是一般只会用到20-30%左右的属性。
如果使用常规的方式开发,css的体积会跟项目的时间呈正相关。

可能有人就说了,css小了,那么html上的class名称变长了。
压缩的原理大家都知道,gzip 的核心Deflate使用的是LZ77 算法和 Huffman 编码来压缩文件,重复度越高的文件可压缩的空间就越大(引用网络)。
我们可以看下facebook的首页,ssr渲染的:

1.4MB->249kB 比较可观的压缩比
学习成本
学习一门新的规范,学习成本肯定是有的,也是需要面对的,不过这些东西可以帮助你快速上手。
cheatsheet速查表
可以快速查询规范中命名所对应的css属性,以及反查css属性所对应的命名。

https://tailwindcomponents.com/cheatsheet/
提示插件 webstorm/vscode
可以在html中提示规范中的属性,以及属性所对应的css

兼容性
目前大多数方案都不兼容IE,其中可能会出现的兼容性问题主要是:
css的新特性:这部分不使用就好
css变量:社区也有相关
浏览器兼容前缀:用autoprefix
所以还是可以继续放心食用
展望与设想
玩过设计的朋友其实知道,组件的设计其实也是一些属性的堆叠,比如颜色,背景,圆角,阴影,在figma中,他们可以统称为token。
这些figma token其实和css属性一样,组合后形成各自的界面
所以,前端视觉还原其实就是人工翻译这些属性(figma token)的过程
当前阶段我只能称为人工翻译,因为对于相同组件,不同人翻译的css表达肯定都有所不同
那么这个阶段是否可以用机器翻译呢?
是的,可以的,可以看下下面这张图片,在figma中,相关的视觉设计规范可以翻译成对应css规范

如果这个规范可以实现同步,那么UI变动后,前端还原滞后的问题能够有所改观。
想象一个场景:
设计需要做情人节活动,所有link和border要改成pink,只需要修改下figma的规范,同步到前端,前端做好审查然后发布,不用一个个找颜色修改了,完美!!
当然,这个想法目前正在实践中,后续有进展会继续出文章分享
大家如果有什么想法可以一起讨论下。
参考:
Facebook 重构:抛弃 Sass / Less ,迎接原子化 CSS 时代