前段时间公司在针对新项目做技术选型和分享时,许多同学都提到了当下非常火热的原子化CSS方案- TailwindCss,但是与此同时,在方案提出的会议上,也有不少同事针对这个轮子提出了自己的质疑,大家各执一词,貌似都没法以压倒性的论证来说服对方。
对于一个已经在多个项目中使用TailwindCSS的人来说,我自然是站在了支持的一方。但是社区每一个技术方案或者轮子的提出,往往都会伴随着各种争议和质疑。所以在会议过后,我查阅了许多资料,也结合了自己的亲身实践的经验,还是决定通过一篇文章来谈谈我所理解的TailwindCSS带给项目开发的真正优势。
Tailwind CSS是什么?
TailwindCSS是目前社区非常流行的原子化CSS方案,他以Postcss插件的形式接入到项目当中。开发者在使用时,仅需要根据样式来书写类名,便可以实现任意的样式组合,最终在项目打包时以增量编译的形式生成最终的样式文件。
我们先来学习下Tailwind Css的接入方法:
安装:
js
npm install -D tailwindcss postcss autoprefixernpx
npx tailwindcss init
创建postconfig:
js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
}
}
创建tailwind.config:
js
module.exports = {
content: ["./src/**/*.{html,js}"],
theme: { extend: {}, },
plugins: [],
}
创建全局CSS文件:
less
@tailwind base;
@tailwind components;
@tailwind utilities;
在项目中使用:
scss
<h1 class="text-3xl font-bold underline"> Hello world! </h1>
具备优势
1、有效减少CSS体积
众所周知,CSS文件的下载和解析虽然不会阻塞DOM Tree的解析,但是却会实打实的阻塞页面的渲染,这实则上是浏览器为了优化重绘和回流的频率所做的优化机制。由此,我们可以很清晰的知道,CSS的文件体积
是性能优化的一个关键问题。
我相信有过维护大型项目的或者接入过大型组件库的同学都知道,项目打包出来的CSS文件或者代码,往往会伴随着页面的增加而变大,进而最终影响页面的性能,其中最为明细的指标则是首屏的渲染时间FCP
。而针对这个问题, 最有效的方法其实就是提高样式代码的复用性
,以及在编译层面上尽可能的删除掉不需要的CSS代码。
我们来看个例子: 在利用less或者scss来编写样式时,我们经常会编写如下代码:
css
.containerA {
position: relative;
width: 100px;
height: 100px;
}
.containerB {
position: relative;
width: 100px;
height: 200px;
}
我们可以看到,这里的position: relative;
和 width: 100px;
是完全重复的,并且在真实开发时也不会有人将这两个样式抽离出一个公共的className,因为这仅仅重复了两行。但是随着页面和组件的不断增加,必然会有越来越多的诸如position: relative;
及 width: 100px;
等常见的样式加入到项目当中,而最终打包到CSS文件里时,这些重复的样式代码也不会被去除,最终造成CSS代码庞大。
而TailwindCSS作为一个原子类的CSS方案,在机制上就杜绝了这种情况的发生,也就是说,无论你的项目使用了多少次position: relative
,最终的CSS文件中都只会存在一个这个样式代码:
css
<div class='relative'>hello world</div>
.relative {
position: relative;
}
而这个代码复用的优势会随着你页面数量的增多而不断放大。
那组件库呢?回到日常开发当中,在我们使用社区或者部门内部开发的原子组件库时,我们往往只会用到库里的几个组件及其样式,这时候,我们必然需要避免引入全量的CSS文件,有什么方法呢?
- 手动引入
arduino
import {Button} fromr 'antd';
import 'antd/esm/button/lib/index.css';
- 使用
babel-plugin-import
在编译时手动帮我们引入,当然这种方式需要组件库本身支持(CSS文件的命名和文件目录保持一致)。
vbnet
plugins: [
['import', {
"libraryName": "antd",
"style": true,
}]
]
但是,如果组件库也使用了TailwindCSS进行开发呢,那么这个问题就变得非常简单了。
ini
<button class='w-1 h-1'>确定</button>
举个例子, 假设我们有一个Button组件,利用到了TailwindCSS进行样式的开发,那么在最终在组件库
打包的时候,这个组件的样式我们不需要打包成css文件,是的,这个组件库最终并没有打包CSS,所以在使用的时候也就不存在引入CSS文件的问题。
他的样式
只需要在项目最终打包
的时候跟随着业务代码一起打包,然后再生成最终的CSS文件就行。这样一来,我们直接解决了上述所说的两个问题:
- 在使用组件库时,增量引入所使用到的组件及其样式。
- 因为项目和组件本身都使用了TailwindCSS进行开发,所以也就共享了彼此的原子类,最终更大程度上的提高了样式代码的复用性。
2、CSS模块化
在我们现在书写业务代码,特别是某些单页面应用的时候,样式污染
一直是需要我们特别注意的问题。
举个例子:
我们在书写单页面应用的代码的时候,模块A和模块B都需要书写某个字体的颜色样式,由于单页面应用的特点,他们的CSS代码最终都会在一个上下文中被解析和执行,这时候后加载container
就会覆盖前一个加载的container
,也就是样式污染问题。
css
// 模块A
<div class='container'>你好, A</div>
.container {
color: red;
}
css
// 模块B
<div class='container'>你好, B</div>
.container {
color: blue;
}
当然,针对样式污染
,社区上已经具备了相当多成熟的方案,主要分为了两大类:
- CSS Modules
- CSS In JS
然而,这些方案在不同维度上其实都不可避免的存在一定的问题,=。
例如CSS Modules
实则是利用了计算Hash值的方式将你书写的Css类名更改成全局唯一的值,虽然有效的解决了样式污染问题,但是需要使用全局样式时,需要额外的定义:global
的书写方式,以及每个模块都需要单独的引入css文件。
而CSS In JS则是开辟了一条新的道路,利用JS的模块化能力来曲线救国
的去解决样式污染问题,这样的优势非常明显,CSS代码书写起来非常灵活,可以有效的利用JS的逻辑,比如函数和变量等,但劣势也同样非常明显,CSS in JS需要在运行时层面上去动态计算每个组件的样式Hash
,以及动态插入到Html文档中,最终造成性能上的损失。
而在Tailwind CSS面前,其实根本存在样式污染问题。因为你书写的每一个样式,最终反映到页面上时,都是一个个独立存在的class,本身就是高度原子化和零耦合化,也就不存在任何污染
的问题。
3、设计层面的统一
在业务层面上,我们无论在公司内部也好,还是独立开发也好,最常听到的一句话便是尽可能避免重复造轮子
。但是,我们也需要认识到,视觉层面上的避免重复造轮子
也同样重要。那么作为一个开发,我们怎么样可以利用技术层面上的影响力去推动视觉设计层面上的统一呢?
TailwindCSS也许是一个很好的选择。
视觉统一
首先,在设计同学下发给前端设计稿的时候,往往会存在三种类型的规范:
- 色彩规范,页面会存在几个主要的色调,也就是我们日常所说的
主题色
。 - 排列规范,定义了不同元素的之间排列间隔等规范,也就是
栅格容器规则
。 - 字体规范,定义了页面存在了哪几种文字样式,包括字体大小、字重、行高等。
那么怎么根据这些东西,去提高我们开发的效率以及页面上的视觉统一,我们这时候可以利用到TailwindCSS的配置文件。
例如根据这一份视觉文件,我们可以在配置文件中定义(其他字体和排列的规则也是大同小异):
less
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
colors: {
grey1: '#ffffff',
grey2: '#fafafa',
grey3: '#f5f5f5',
grey4: '#f0f0f0',
grey5: '#d9d9d9',
},
plugins: [],
}
这样一来我们便可以快速的在代码中使用统一的色彩规范,另外在后期去适配多主题或者主题变量等需求时,只需要动态的去更改这份配置文件便可。
ini
<p class='text-grey1'>你好</p>
响应式统一
每当我们需要实现一套适配各个分辨率的页面时,尤其是PC类的页面,书写各种各样冗余的,复杂的@media
媒体查询代码很大程度上是不可避免的。
例如,我们想在不同分辨率下,控制某个元素的宽度,我们往往需要以下CSS代码:
css
<div class='container'>你好</div>
.container {
width: 100px;
}
@media (min-width: 640px) {
width: 20px;
}
@media (min-width: 768px) {
width: 50px;
}
并且,如果每个页面或者模块的类名都不近相同,响应式所针对元素的也不近相同时,书写媒体查询的代码就会越来越多,变成一些很难阅读,也很难维护的代码。
但是在TailwindCSS中,这些问题可以很有效的解决,他支持在各个分辨率的断点中,去动态的适配各个原子类,进而来实现,颗粒度非常细的响应式样式代码。
例如,针对上个例子中的CSS样式代码,我们可以改写为:
less
<div class='w-[100px] sm:w-[20px] md:w-[50px]'></div>
这是因为在TailwindCSS中自定义了五个断点,我们仅需在书写类名前加上需要适配的断点类型即可。
另外,当然也支持在tailwind.config.js
中自定义我们自己需要的屏幕断点:
css
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
screens: {
'custom': '1440px',
// => @media (min-width: 1440px) { ... }
}
},
plugins: [],
}
技术输出
当我们根据设计稿,完成项目的整体视觉架构开发时。我们的tailwind.config.js
配置文件,实际上已经变成了 一套完整的,从设计稿到代码层面上的规范映射。而这套映射实际上,可以复用到许多的大大小小的项目当中,最终去推动整个小组或者部门的视觉层面
上的统一,无论是开发效率,用户体验上都能够得到很大的提升。
那么,我们如何快速的去推广和让业务方接入我们的视觉体系呢?
这时候我们可以利用到TailwindCSS中的插件体系,TailwindCSS允许我们通过Plugins
的形式,去扩展自身的能力,包括添加基础类,以及修改CSS代码的能力。
javascript
const plugin = require('tailwindcss/plugin')
module.exports = {
plugins: [
plugin(function({ addComponents }) {
addComponents({
'.btn': {
padding: '.5rem 1rem',
borderRadius: '.25rem',
fontWeight: '600',
}
})
})
]
}
换句话来说,我们只需要将我们已经统一的视觉方案
打包成一个插件,让业务方利用TailwindCSS
快速接入即可。
4、省去命名烦恼
利用TailwindCSS的原子类的能力,我们在书写类名的同时也就是在书写样式,所以也就不需要再为我们的各个元素,去定义各种各样的名字了。
arduino
// 需要定义一个name的className
<p class='name'>张三</p>
// 不需要思考取什么名字,直接根据样式写出对应类名即可
<p class='text-red'>张三</p
每当谈到TailwindCSS时,我们总会谈到这一点。但是有很多同学不认为这个是一个优势,觉得这是一个不痛不痒的东西。但实际在我个人的开发过程当中,根据每一个标签的含义和作用域(例如container
和container-header
),去取一个名字,然后再根据这个名字去定义样式,是一个十分繁琐的过程。在切换到TailwindCSS之后,简直不要太爽。
质疑
在讨论完TailwindCSS所具备的各个优势时,我们再来谈谈大多数同学对TailwindCSS所提出的一些质疑点。
CSS文件体积转移到了HTML文件上?
诚然,页面的性能不可能只取决于CSS的体积大小上,HTML文档下发的体积也同样重要,解析HTML文档的速度也是FCP
的一个关键因素。
理论上来说,我们必须承认,原子化的CSS方案,都会或多或少的增大HTML的文件体积,因为我们总归是将样式代码从CSS文件里挪用到了HTML的标签里,即使原子类的命名有多么简短,也依旧是不可避免的。
但是,我们还需要考虑到另外一个因素,GZIP压缩。
当我们从CDN上下载我们的文档时,我们往往是拿到了GZIP压缩后的一个产物。而一个有意思的点是,GZIP的压缩效率
与文档的字符重复程度
呈正相关,换句话来说,也就是当文档里的重复字符越多时,文档压缩后的体积就会越小。
而作为原子类的CSS方案,TailwindCSS恰恰具备这一特点。我们所书写的class
,绝大多数都会重复定义在多个标签内,这样便可以充分的利用到GZIP压缩的优势,大大减少类名所能够造成的体积影响。
代码的阅读性和维护性?
css
<div class="bg-[#e8effc] h-[56px] text-grey1 font-[500] text-[20px] pl-[16px] rounded-[8px] mt-[6px]">
</div>
无论是谁,当维护别人的项目时,看到这一长串的类名代码,都会难免头大。不可否认,HTML标签过于冗长的现象,的确是原子类CSS方案带来的后果之一。
但是,我们需要去理性分析一下,造成这个现象的最主要原因真的只是因为使用了TailwindCSS
吗?
在我看来,非也。即使在书写普通的CSS文件时,也一样会有许多同学写出一些非常长且难以阅读的CSS代码。这里面其实还存在了许多其他因素:
- 没有有效的利用到样式继承
- 没有有效的利用到响应式布局
- 没有做到有效的样式封装和抽离
其实,TailwindCSS
本身针对这种状况,也允许开发者自定义许多基础类和集合类,依次来减少冗长的类名代码,
例如,我们便可以在global.css
中去封装一个集合类:
less
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.flex-center {
@apply flex justify-center items-center
}
}
当我们在代码中需要实现水平和垂直居中时,我们仅需要书写flex-center
一个名字即可:
css
<div class='flex-center'></div>
学习曲线?
结合非常完善的TailwindCSS文档,以及Vscode插件的语法提示,我认为不存在很陡峭的学习曲线,在写完一个项目之后,我现在基本可以脱离文档使用。