OOCSS
-
OO(“Object Oriented”):面向对象。
-
OOCSS:Object Oriented css(面向对象css)的缩写,是一种用最简单的方式编写的CSS代码,从而使代码 重用性,可维护性和可扩展性更好的书写方法。
OOCSS 解决的问题
很多开发者在编写 CSS 时,经常会遇到以下问题:
-
样式重复: 同样的样式在不同的地方重复定义,导致代码冗余。
-
代码难以维护: 随着项目规模的扩大,CSS 代码变得越来越复杂,难以维护。
-
样式冲突: 不同组件之间的样式容易互相干扰,导致样式冲突。
但 OOCSS 则将CSS代码模块化,大量减少了样式重复和冲突的问题,且做到了修改一个模块可以同时修改多个组件,增加了可维护性。
OOCSS 总体思想
OOCSS 的核心思想是 通过将 CSS 代码模块化,提高代码的复用性和可维护性。且各个模块之间不相互影响,模块也可以随时随地使用。
所以 OOCSS 的代码风格就可以浓缩为:
-
零件多而散: 一个DOM元素上可能挂了许多个类名才能拼接出一个效果。
-
不使用继承选择符: 既然要“随时随地用”,当然不能受到“继承”关系的制约,所以OOCSS几乎不适用继承选择符。
学习 OOCSS 需要了解其两大基本原则:
-
分离结构与外观 / Separate structure and skin
-
分离容器与内容 / Separate container and content
分离结构与外观
分离结构与外观的含义就是:一个组件的结构(如宽高、边框、边距等)和外观(如背景、颜色等)应该分开定义在不同的 CSS 类当中。
这样做的好处就是,当你想给组件换个“皮肤”,就不会再受组件结构相关CSS的影响。你只需把控制外观的类更换一下即可实现换肤。
这样一来,你能构成的组件样式就有结构样式数 × 外观样式数 个,感觉是不是比一个个写好多了?这就是增加了代码复用性。
分离容器与内容
分离容器和内容实际上就是要求所有样式尽可能脱离它的内容,不管一开始它是为谁而服务的。
比如一开始专门为一个卡片.card
设计了一个按钮,命名为.card-btn
。但是也许这个按钮其实非常通用,卡片的前缀反而局限了这个按钮的用途,此时就是“容器”与“内容”深度绑定了。
另外一个更极端的案例,那就是使用继承****选择符了,例如.card .btn
。这不仅在命名上局限了按钮的用途,甚至局限了其在DOM结构中的使用。
所以若要遵守分离容器与内容,应当做到:
-
不使用继承选择符(形如
.container li{...}
的结构),以达到子元素即使离开了容器也应该能正确显示的效果。 -
尽量不给样式限定用途 以让样式具有可复用性。
BEM
BEM(Block Element Modifier)是一种常用的CSS命名约定,用于帮助开发者更好地组织和管理CSS代码,其本质就是进阶版的OOCSS。
BEM将页面的视图分为块(Block)、元素(Element)、修饰符(Modifier),如何理解它们之间的关系呢? 如下图所示:
-
块(Block):它代表功能独立的整体,是一系列结构、表现和行为的封装。块的类名应该使用简短而具有描述性的名字(如:.tabs、.card)
-
元素(Element):顾名思义,块内部的内容就是元素,它代表块中的组成部分。元素的类名由块名和元素名以双下划线(__)进行连接(例如:.card__title、.tabs__item)。
-
修饰符(Modifier):如上图所示第三个元素,它拥有个性化样式,我们需要单独为它添加样式。通过添加修饰符类,我们可以轻松修改块或元素的外观、状态。修饰符的类名由块或元素名后面加上连字符(--),然后加上修饰符名称(例如:.card--highlighted、.button--disabled)
<div class="card"><p class="card__element">这是一段文本内容。</p><p class="card__element">这是一段文本内容。</p><p class="card__element card__element--primary">这是一段文本内容。</p>
</div>
SMACSS
SMACSS(Scalable and Modular Architecture for CSS)是一种CSS设计模式,旨在帮助前端开发工程师更好地组织和管理大型项目中的CSS代码。
SMACSS的核心思想是将项目样式代码划分为不同的模块,并根据其功能和作用进行分类。这种分类使得样式的结构更清晰,对于新的需求或变化的需求可以更容易地进行修改。
下面是SMACSS的五个主要原则:
-
基础(Base):基础样式主要是对浏览器默认样式的重置。
-
布局(Layout):布局样式定义了页面的整体结构和布局,如头部、侧边栏、页脚等。
-
模块(Module):模块样式定义了可重复使用的独立组件,如导航栏、卡片、按钮等。每个模块应该具有自己的封装和独立性,以便于在不同的页面上进行复用。
-
状态(State):状态样式定义了元素在特定状态下的样式,如鼠标悬停、选中、禁用等。状态样式可以通过添加类名或伪类来应用,并且应该与模块样式分开。
-
主题(Theme):主题样式定义了页面的整体风格和视觉差异,如颜色、字体等。主题样式可以根据具体的项目需求进行定制。
注意:SMACSS设计模式核心思想是分类。至于在实际工作中CSS代码是否完全按照这5个原则进行划分则无关紧要,比如说项目中没有主题要求,则完全可以不考虑主题层级。
ACSS
ACSS 的概念
ACSS 是 Atomic CSS 的简写,它是 Thierry Koblenz 在 2013 年 10 月的文章 Challenging CSS Best Practices 中创造的。
首先,让我们为 原子化 CSS (Atomic CSS) 给出适当的定义:
John Polacek 在文章 Let’s Define Exactly What Atomic CSS is 中写道:
Atomic CSS is the approach to CSS architecture that favors small, single-purpose classes with names based on visual function.
译文:原子化 CSS 是一种 CSS 的架构方式,它倾向于小巧且用途单一的 class,并且会以视觉效果进行命名。
除了叫 ACSS,你还可以称它为函数式 CSS,或者 CSS 实用工具。
CSS 是一个不强调逻辑,而更侧重表现的一门所见即所得的语言,当样式写多了,你就会发现常用样式的来来去去也就那几个,无非就是调整一下他们的排列组合。每次写这些重复的样式代码我就感觉自己是在重复造轮子,自然而然就产生了想要缩写的需求,而 ACSS 做的一些事情很平常,无非就是把 CSS 属性写成一个独立的类名。
ACSS 和 CSS-in-JS 为什么会火
前面我们明白了 ACSS 的概念,所以接下来我要讲下 CSS-in-JS 的概念,然后才好解释为什么它们会火。
CSS-in-JS 是很重要的概念,本来打算写篇文章介绍的,题目都取好了 「CSS 架构之 CSS-in-JS」,整理资料发现阮一峰老师写过了,那我就直接拿过来吧 阮一峰——CSS in JS 简介,但是阮老师并没有给出流行 CSS 的解决方案,现在都 21 年了,我们知道目前流行着好几种解决方案,方案各有利弊,我们需要一篇文章来通透的理解它们,于是 @FateRiddle 同学的 React拾遗:从10种现在流行的 CSS 解决方案谈谈我的最爱 (上) 这篇文章出现了。
你可以先不看上面的文章链接,我来给你梳理下:
很久以前,前端项目比较小,HTML、CSS、JS 都耦合在一起,后来随着项目越来越大,为了便于维护,代码不允许在耦合,要求各个技术只负责自己的领域。
在后来,伴随着 React 出现,前端组织代码的方式变了,组件成为组织代码主流方法,而组件的核心原则就是代码完全不依赖外部,表现在 React 中就是 HTML、CSS、JS 强强耦合,这样就避免了影响其他组件,对于 CSS 我们也写在了 JS 中,这就要 CSS in JS,其实就是写行内样式。
但行内样式不支持伪类、媒体查询,于是出现了 React-JSS 这种库,对行内样式进行扩展;有人又不能忍受 React-JSS 这种样式驼峰的写法;出现了 styled-components,遵循 CSS 写法规范的库;有人比较喜欢不耦合的写法,于是 Css Module 出现了;还有人觉得 Vue 的解决办法比较优雅,然后就出现了 styled-jsx。
我来总结下:
CSS-in-JS 本质就是行内样式,之所以会火就是因为组件化时代的到来。
看明白 CSS in JS 火的原理,你肯定猜到 ACSS 会火的原因——那就是组件化时代的到来,你甚至可以理解为 ACSS 就是 CSS 架构下得 CSS 组件化。
在没有组件化的传统网页开发时代,如果你通过 ACSS 来确定样式,例如下面代码的形式,合作的小伙伴肯定以为你疯了:
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">按钮</button>
因为 button 的复用率很高,你项目到处充斥着这种 button,一旦 button 要修改某些样式,你可去哭娘去吧,这哪有直接给个 .btn 类名方便,要修改直接改类名就行了,例如下面:
<button class="btn">按钮</button>
但是在组件化时代就不一样了,例如使用 React 封装一个 Button:
const Button = ({ children, color }) => ( <button class=`bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded ${color}`>{children}</button>)
使用如下:
<Button color="pink"> 注册 </Button>
如果样式有修改,我只要插拔 ACSS 就行了,而且对比使用 .btn 实现,样式的重用性会极大提高,理解也很容易。
这下你明白了吗,要说 ACSS 和 CSS-in-JS 为什么会火,就是因为***组件化***。
ACSS 优劣
使用 ACSS 的好处:
-
你的 CSS 停止增长。使用传统方法,每次添加新功能时,您的 CSS 文件都会变大。使用实用程序,一切都是可重用的,因此您很少需要编写新的 CSS,一套样式全局通用。
-
你不是在浪费精力发明类名。不再添加愚蠢的类名,例如
sidebar-inner-wrapper
只是为了能够设置样式,也不再为真正只是一个 flex 容器的东西的完美抽象名称而苦恼。 -
灵活,易读。CSS 是全球性的,当你做出改变时,你永远不知道你破坏了什么。HTML 中的类是本地的,因此您可以 插拔式改变样式 而不必担心其他问题,CSS 样式很多缩写更加符合大脑的记忆。
-
永远不用担心命名冲突,永远不用担心样式覆盖。
使用 ACSS 劣处:
-
毫无疑问,ACSS 会增加HTML 的体积,但是借助 Gzip 这个就不是大问题。
-
熟悉命名 ACSS 命名会有一定成本。
ACSS 劣处是非常小的,而好处有非常大,没有理由在项目中不适用,强烈建议你每个前端项目都是用 ACSS。
如何选择 ACSS 库
市面上有不少成熟的 CSS 框架,如 Tailwind CSS,Windi CSS 以及 Tachyons 等。
同时有些 UI 库也会附带一些 CSS 工具类作为框架的补充,如 Bootstrap 和 Chakra UI。
甚至还有一些人根据项目总结出来自己的 ACSS,例如 atom.css、SACSS: Static Atomic CSS 等。
ACSS 库大致就分为这三类了。
把它们整合到我们的项目,那我们选择的标准是什么呢?
-
按需生成,比如我们使用
class="m-1"
来设置 margin,那么 m-x,x 到底是多大呢,x 但不管 x 是多大,当增加 x 的时候,margin 不同方向,比如mt
代表margin-top
,mb
代表margin-bottom
等,也得增加,如果加上:hover
和:focus
这样的伪类时,体积还会得更变大,原子类太多了,应该提供按需生成只加载我们用过的。 -
动态化,原子类不应该是完全静态化的,比如我要使用
class="m-100"
,我应该可以是直接使用,而不是设置完之后,发现样式没生效,然后通过框架的配置文件,去增加对 m-100 的支持,原子类要把可插拔做到极致。
除了上面两个是非常重要的标准,我认为 自动值推导 和 属性化模式 也是提升了开发体验要考虑的部分。
我们来看看我们最终会选择哪个 ACSS 库,首先原子 CSS 一定要纯净,所以 UI 框架附带的 ACSS 就不能采用了,根据项目总结的 ACSS,它的原子 CSS 太过静态,不能随想随用,不符合原子类不应该是完全静态化的标准,Tailwind CSS 本来是没有按需生成的,后来增加了,但是 Windi CSS 速度更快还兼容 Tailwind CSS,所以我们很自然就必须必的选择了 Windi CSS 。
总结
我们先通过举例子,了解了 ACSS 的使用,然后介绍了 ACSS 的概念,通过对比 CSS-in-JS 来剖析 ACSS 借助前端组件化浪潮开始起飞的过程,最后如何在项目中选择自己的 ACSS 库,我们通过一些硬性标准,分析了三类 ACSS 库,帮你选择了 Windi CSS 。
ITCSS
ITCSS(Inverted Triangle CSS)设计模式的起源可以追溯到2013年,由Harry Roberts提出。Harry Roberts是一位著名的前端开发专家和顾问,他在实践中发现,许多项目在处理CSS时都存在类似的问题:样式表无序、样式冲突、复杂度高、维护困难等。
为了解决这些问题,Harry Roberts提出了ITCSS设计模式,其灵感来自于CSS的层叠特性。通过将样式表按照特定的层次结构组织起来,使得样式规则更具层次性和可维护性。那他具体是如何处理的呢?
ITCSS的核心思想是分层,目前普遍意义上把项目划分成了七层,如下:
1、Settings(设置):在这个层次中,我们定义项目中的变量、配置和全局选项等。这些设置可以是颜色、字体、间距等通用的样式变量,供其他层次使用。
2、Tools(工具):在这个层次中,我们定义一些辅助函数、混合器(mixins)或者其他工具类。例如 SCSS 中的 @mixin 、@function。
3、Generic(通用):在这个层次中,主要是重置浏览器默认样式(normalize.css),或者标准化样式(reset.css)。
4、Elements(元素):在这个层次中,主要是根据自身项目需要 对一些元素进行定制化的设置,例如重新覆盖A 标签默认样式等。
5、Objects(对象):在这个层次中,我们定义可复用的、独立的样式模块。相当于SMACSS中的Layout。
6、Components(组件):在这个层次中,我们定义特定的页面组件,和Element UI这种组件库单独为每个组件定义样式相似。
7、Trumps 层: 根据实际需要设置 important! 的地方。
但这七层也不都是必须的,而是根据实际的项目需要去划分的。下面我们以Element-plus为例,来看下在实际的项目中,ITCSS设计模式是如何运用的。
ITCSS 要解决什么,有什么特点
学完了概念,看完了实战,我们来深入 ITCSS 的精髓,Harry Roberts 发明的 ITCSS 不是一个框架,只是组织样式代码的一种方案,既然是方案,那么我么就应该研究下它出现到底是为了解决什么,没错还是 CSS 的老问题,就是 CSS 不支持模块化,全局只有一个作用域,由此导致样式覆盖、混乱。
为了解决,作者发明了 ITCSS,并总结了它三个关键指标来帮你理解。
- 通用到显式——explicitness
观察 ITCSS 的分层,我们发现层的权重是层层递进,作用范围却是层层递减,例如,刚开始我们会为浏览器表现一致的样式,采用标签选择器和属性选择器等进行样式重置,落到项目最底层,我们就完全针对当前样式进行修改的。例如先从通用的 h1~6 {}
,到非常明确的规则 .text-center {}
。
- 低特异性到高特异性——specificity
最低特异性选择器在开始时出现,随着我们的项目进展,特异性稳步增加。我们希望确保尽可能多地避免特异性战争,因此我们尽量避免在低特异性选择器之前编写高特异性选择器。我们总是在同一个方向上添加特殊性,从而避免冲突。
- 深远到本地化——reach
项目开始时的选择器会影响很多 HTML 的表现,随着代码的增加,影响范围逐渐缩小。
我们可能首先擦除所有内容的边距和填充,然后我们可能会为每种类型的元素设置样式,然后将范围缩小到应用了特定类的每种类型的元素,依此类推。正是这种逐渐缩小的范围给了我们三角形。