CSS 的发展历程可以说是一部开发者不断思考与实践的历史。从最初的层层嵌套,到组件化 CSS,再到工具类优先的 Tailwind CSS,开发模式的每一次变革都在尝试解决现实需求中的痛点。
在这个过程中,我们见证了从语义化命名到面向视觉内容的转变,也逐渐认识到效率与可维护性的重要性。这篇文章不仅是对 CSS 演变的回顾,更是对高效开发的探索。希望通过分享我的经验,能帮助大家在开发中找到适合自己的工具与方法。
CSS如何走到今天,什么是好,什么是坏?
阶段一:语义化的CSS,嵌套式的结构
我们往往要跟着html的结构去写一层层嵌套的css,有时是为了不污染其他模块的样式,有时我们需要为了去描述我们想实现的这一模块去绞尽脑汁的起名字,这样那样的英文单词。 所以你会发现,我们起的名字几乎与我们的样式无关,只和语义化有关,曾经有时遇到语义化比较复杂的,我还喜欢取长长的英文单词,以体现我们前端工作者的专业:)
.star-welcome {
color: red
> img {
width: 100%;
display: block;
}
.star-text{
.star-welcome-text: {
...
}
}
}
.star-swiper {
...
}
阶段二:样式与结构分离,扁平的块级css
此时的css依然是语义化的,但是我们不需要再去关注CSS的一层层嵌套。
由此带来一个问题,如果再来一个star-admin
他们的样式差不多,但是如果写star-user
就会有点奇怪。于是我们做出解决方案,复制一份。但这样又显得很蠢,聪明一点的孩子,可能会使用类似于@extend
的方法,来继承前一种样式。爱干净的孩子,可能会提炼一个不那么精确的css类名,比如star-profile
<div class="star-user">
<div class="star-user__profile"></div>
<div class="star-user__name"></div>
<div class="star-user__intro"></div>
</div>
.star-user {
text-align: center
}
.star-user__name {
font-size: 22px
}
.star-user__intro {
background: green
}
阶段三:css组件,与内容无关+CSS预处理器
此时的我们已经发展到了一个阶段,就是尽可能的创造更多可重用的css类名及样式,比如.mask, .profile-image, .normal-text, .button
等。就像antd,element内,类似于btn btn--primary
,此时我们已经省力很多了。但当我们写完一个CSS组件时,发现视觉稿已经变样了,样式不一样的时候很难重用。而CSS预处理器的出现,极大的提高了我们开发CSS的效率,主要围绕在嵌套、变量、mixin、模块化等方面。
阶段四:面向视觉内容的CSS工具类
仔细一想,CSS真正需要解决的问题,是视觉问题,是面向真实视觉稿的距离、颜色、字体等这些问题。我们需要focus 的,不应该是取花里胡哨的名字,而是在开发时能快速还原视觉稿。并且能够在约束和自由之间寻找一个平衡点,让我们的网站看起来是有一个专业的、整洁的、标准化的一个实现。我想这就是tailwind作者开发时的初衷。
<button onClick={addFriendsClick} className="mx-auto mt-[3.33vw] h-[3.33vw] w-[23vw] font-primary text-32px-vw font-bold uppercase text-[#A4DFF2]" > Add </button>
切入正题
由题可知,我们今天讲的是Tailwindcss,应该很多同学都已经在用了。Tailwindcss 是一款基于postcss开发的插件,他是基于 Atomic/Utility-First CSS 标准的,这是一种标准,但他不是你必须遵守的那个标准,还有些其他的标准,比如语义化(Semantic),但目前就我个人而言我挺喜欢这个标准,我觉得它对我的开发效率有所提升。 Atomic意思是原子,我理解的意思就是指拆得很细,实用优先,提供的样式类细的粒度就像原子一样?虽然你可能会在使用它之后发现似乎没有太多“新”的东西,但感受上就是让你感觉很舒服。在用它开发了几个项目之后,我觉得还是值得推广的。
使用Tailwindcss 可以收获什么
1、可以很方便做多端适配及响应式开发 2、无需关注命名 3、拓展性强 4、方便后期维护迭代 5、打包后的CSS体积小 6、已经整合了大量工具类,方便自由组合 7、在熟悉命名规则之后,还原视觉稿效率提高(可以装一个Tailwind CSS IntelliSense插件) 8、不用来回切换CSS和JSX/HTML,可以专注的写页面 9、修改某一块样式时一般不会影响到其他样式
需要注意的几点
1、如果不整理封装提取的话,HTML上很可能会让你眼花缭乱,颗粒度太细了,因此这既是优点也是缺点
2、刚入手时需要频繁查阅API文档,习惯就好
3、如果类名是动态拼接出来的,则无法渲染
4、有些样式类没有,需要自己定义或者手写新增类名,或者复杂一点的样式就无法编写比如 .star-content:hover .star-nav {display: none}
如何应对不好的点
1、针对粒度太细的问题,我们需要学会总结整理,善用@apply,复用已有的css类,比如
.star-button {
@apply w-20 h-5 mt-2 px-4 py-6 bg-red-500 ...
}
2、有些类名需要从服务端动态获取,再拼接出来,直接渲染一般都显示不出来,可以采用’clsx()‘等工具库来动态计算className
字符串,一般可以解决问题。如果实在不行,就用style来计算渲染。
3、我们可能无法避免的需要写几个CSS来处理一些比较难处理的情况,比如你想通过::before, ::after等来创建一些小图标等
如何写好Tailwind
1、尽量按照 定位/可见性、盒模型、边框、背景、排版、其他调整的优先级来书写,这样有助于解析类名的速度提升,以及可读性提高
2、尽量能少写就少写,比如用:
px-2
来替代pl-2
+ pr-2
3、你要写屏幕适配时,需要给每一个类前面添加断点比如你需要写flex md:text-center md:text-red-500 md:mt-2
而不是 flex md:text-center text-red-500 mt-2
4、尽量使用padding 而不是magin,这样可以避免边距折叠问题(两个块相邻的外边距被合并为一个单独外边距)
5、如果时间允许,尽量把需要新增的样式类添加到tailwind.config.js
内,而不是直接写w-[10px]、mt-[20px]
这种的,这一步是为了全局统一性和设计的一致性。
如何快速还原多端px设计稿并做好适配
笔者使用vite + ts + react + vw + tailwindcss
进行开发,刚开始为了适配移动端和PC端,需要对不同设计稿的尺寸进行VW换算(设计稿元素尺寸 / 设计稿宽度) * 100VW
,使用不同的断点编写不同的尺寸,这其中计算这个数值还是相当麻烦。
类名可能是这个样子:className=“h-[2vw] w-[2vw] rounded-full md:h-[4.8vw] md:w-[4.8vw]”
于是我决定采用postcss-px-to-viewport
来自动转换px至vw。这样【自动计算+tailwind快速编写类名+快速适配移动PC】的开发模式,会在一定程度上提高编写效率。
但在我使用这个插件的时候遇到了一个问题,Vite config里属于编译时,PC或者移动端属于运行时,我无法利用postcss-px-to-viewport
这个插件动态设置viewportWidth
来输出一套既符合移动又符合PC的样式,如果硬要,可能我需要打包出两套三套或者更多。比如我可能需要设置环境变量,大概这样SCREEN_WIDTH=1920 vite build
亦或是转而再回头使用rem,但这都不是我想要的方案。
后来仔细看了看tailwind打包出来的类名,以及postcss-px-to-viewport
的源码,发现我可以根据渲染出来的类名的开头去匹配不同的设计稿尺寸,这样就可以一并把生成的多端类名计算为不同的vw的值了。
于是我改了改了这个插件,核心思想就是正则匹配获取tailwind渲染出来的带有前缀的类名,根据不同的类名对应不同的视觉稿,计算不同的VW值。已上传至NPMstar-px-to-viewport - npm名为star-px-to-viewport,需要请自取。当然,这个插件需要和tailwind配合才能发挥其威力。
调试过程抓取的类名如下:
部分旧的插件源码展示,
var unit;
var size;
var params = rule.parent.params;
if (opts.landscape && params && params.indexOf('landscape') !== -1) {
unit = opts.landscapeUnit;
size = opts.landscapeWidth;
} else {
unit = getUnit(decl.prop, opts);
·····在此处匹配不同的设计稿尺寸······
size = opts.viewportWidth;
}
var value = decl.value.replace(pxRegex, createPxReplace(opts, unit, size));
此时,我们的开发页面就可以直接根据视觉稿的px尺寸来了,并且配合tailwind,可以做到高效开发
在JSX中可以这样写,具体数值仅供参考,此处仅展示用,各种样式属性都可以一气呵成编写。可再配合@apply,进行一些常用的整理。
<div className="absolute left-0 top-0 z-50 h-[100px] w-[100px] bg-white sl:h-[100px] sl:w-[100px] xl:h-[100px] xl:w-[100px] lg:h-[100px] lg:w-[100px] md:h-[100px] md:w-[100px] sm:h-[100px] sm:w-[100px]"></div>
在tailwind.config.js
可以这样设置断点,具体设计稿数值可以自己配置并在设置postcss插件时传进去
theme: {
screens: {
sl: { max: "1440px" },
xl: { max: "1280px" },
lg: { max: "1024px" },
md: { max: "750px" },
sm: { max: "640px" },
},
}
编译后会输出如下内容,具体根据自己输入的值的变化而变化
@media (max-width: 640px){
.sm\:w-\[100px\] {
width: 15.625vw;
}
}
@media (max-width: 750px) {
.md\:w-\[100px\] {
width: 13.333333vw;
}
}
@media (max-width: 1024px) {
.lg\:w-\[100px\] {
width: 9.765625vw;
}
}
@media (max-width: 1280px) {
.xl\:w-\[100px\] {
width: 7.8125vw;
}
}
@media (max-width: 1440px) {
.sl\:w-\[100px\] {
width: 6.944444vw;
}
}
在vite.config.ts中的配置
import pxToVW from 'star-px-to-viewport';
import tailwindcss from 'tailwindcss'
import autoprefixer from 'autoprefixer' // 随意
css: {
postcss: {
plugins: [
tailwindcss,
autoprefixer,
pxToVW({
unitToConvert: 'px',
viewportWidth: 1920,
viewportWidthSM: 640,
viewportWidthMD: 750,
viewportWidthLG: 1024,
viewportWidthXL: 1280,
viewportWidthSL: 1440,
unitPrecision: 6,
propList: ['*'],
viewportUnit: 'vw',
fontViewportUnit: 'vw',
selectorBlackList: ['ignore-'],
minPixelValue: 1,
mediaQuery: true,
replace: true,
// exclude: [/node_modules/],
exclude: [],
landscape: false
}),
]
}
},
简单总结
说了这么多,就不在这里具体展开讲tailwind
的使用方式了,大家有兴趣的同学可以直接去官网查看Tailwind CSS 中文文档 官网上已经非常详尽的展示了各种功能与使用方式。以上,是我使用的一些感受与经验总结,与大家共勉。
反思
到底哪开发模式才是适合自己的,如何在效率与质量之间找到平衡,如何在持续学习的过程中规范自己的思想和行为,遇到问题如何解决,我想这是我需要时常反思的。