BEM、OOCSS、ITCSS...

最后修改于

本文在概述的基础上,进一步介绍 BEM、OOCSS、ITCSS 这些用于组织 pureCSS 的范式,也即是 CSS 架构。

尽管习惯上这几种范式一般会与 SCSS/LESS 等预处理器结合使用,但是在概念上,实际上是一种组织 CSS,避免 CSS 变得混乱的方法论(CSS 架构)。SCSS/LESS 更像是高效书写标准 CSS 的方言。

OOCSS#

面向对象的 CSS,推崇结构与样式分离,容器与内容分离。咋一看,这句话非常抽象,还是结合场景来看看。

结构与样式分离#

比如一个 Login 页面,有两个按钮,login-buttonregister-button,如果不考虑复用,一次性实现,可能是这样的。

.login-button {
	background-color: blue;
	color: white;
	padding: 10px 20px;
	border-radius: 5px;
}

.register-button {
	background-color: red;
	color: white;
	padding: 10px 20px;
	border-radius: 5px;
}

这么看似乎也没啥问题。但是如果在一个大型网页中考虑,有很多个button,每一个button都类似,但是不完全一样。这种情况下,一次性实现很快会导致 CSS 变得混乱。

OOCSS 认为,每个 button 有相同的共性。将相同的部分抽离出来,作为一个btn类(结构),然后每个 button 独特的样式,抽离出来作为单独的样式类,如 btn-primary(样式)。

/* 结构类:定义基本结构,相同的部分 */
.btn {
	padding: 10px 20px;
	border-radius: 5px;
}
/* 样式类 */
.btn-primary {
	background-color: blue;
	color: white;
}
/* 样式类 */
.btn-secondary {
	background-color: red;
	color: white;
}

使用时,通过组合不同的 css 类,.btn .btn-primary来实现最终的样式。此为结构与样式分离。

容器与内容分离#

在如下的场景中,一个博客列表,一种有毛病的写法是这样的。

<style>
.blog-list {
  width: 100%;
  max-width: 800px;
  margin: 0 auto;
}

.blog-sidebar-list {
  width: 100%;
  max-width: 200px;
  margin: 0 auto;
}

.blog-list .blog-list-item {
  border: 1px solid #e0e0e0;
  padding: 15px;
  margin-bottom: 15px;
}
.blog-list .blog-list-item p {
  color: #666;
  line-height: 1.6;
}
</style>

<div class="blog-list">
  <div class="blog-list-item">
    <h2>文章标题</h2>
    <p>文章内容...</p>
  </div>
</div>
<div class="blog-sidebar-list">
  <div class="blog-list-item">
    <h2>文章标题</h2>
    <p>文章内容...</p>
  </div>
</div>

典型的问题比如 .blog-list .blog-list-item p这个选择器,pblog-list 组合在一起,是一种不理想的情况。因为 blog-list-item 也可能放在 blog-sidebar-list 中,这时候这个选择器就不起作用了。
很自然的解决方案就是移除选择器中的 .blog-list

/* 移除选择器中的 .blog-list */
.blog-list-item {
  border: 1px solid #e0e0e0;
  padding: 15px;
  margin-bottom: 15px;
}
.blog-list-item p {
  color: #666;
  line-height: 1.6;
}

.blog-list-item作为内容,让.blog-list作为容器,两者拆分。使得 .blog-list-item 也能放在.blog-sidebar-list 中。这也就是所谓的容器与内容分离。鼓励有意识的区分容器和内容,让容器和内容的样式相互隔离,互不影响。

是的,这的看起就像是讲正确的废话,在经过长时间的磨练之后,这更像是一种会自然而然形成的技巧。在我的视角看来,OOCSS 更多的是实现思路上的指导意义,而非某种具体的解决方案。CSS 实现同一样式的路径很多,但这是几种较优的路子之一,它就像是在说 CSS 本就应该如此,选它吧,不会有啥问题。

代表性框架比如 Bootstrap(宽泛一点讲,大部分 CSS 框架实际上都符合 OOCSS 的理念)

BEM#

BEM — Block Element Modifier
类似于 OOCSS,BEM(Block-Element-Modifier)也是一种范式,提倡有意识区分的BlockElement以及Modifier

  • Block 是指页面中的一个区块,比如Header,Container,Menu
  • Element 即是指区块中的元素,比如menu-item,list-item等。
  • Modifier 则是修饰符,用来修饰区块 / 元素的状态,比如isActive, isDisabled
    CSS 类名通过 Block__Element--Modifier 的方式进行组合。
    一种示例如下
<div class="card">
	<div class="card__content">
		<h3 class="card__title">Card Title</h3>
		<p class="card__text">Card text</p>
	</div>
	<button class="card__button">Button</button>
	<button class="card__button card__button--primary">
		Primary Button
	</button>
</div>
<style>
/* Block Style */
.card {}

/* Elements Style */
.card__content {}
.card__title {}
.card__text {}
.card__button {}

/* Modifier Style */
.card__button--primary {}
</style>

可以发现,BEM 跟 OOCSS 并不冲突,并且说的很像是一回事。
我认为对于Block,实际上也就是 OOCSS 中所谓的容器,Element实际上也就是 OOCSS 的内容(容器与内容分离)。只是换了一种说法。而 Modifer 则是一种另类的样式(OOCSS 结构与样式分离)。
区别在于 BEM 推崇一种看起来比较另类的Block__Element--Modifier的命名方式,不过这种组织 CSS 方式很好的与 SCSS / LESS 这样的预处理工具结合在一起,反倒是进一步提高了效率。

SMACSS#

SMACSS(Scalable and Modular Architecture) 将 CSS 规则进行分类,分为 Base、Layout、Module、State 和 Theme 五种类型。

Base#

基本规则,定位类似于用户代理样式表。定义元素的默认值,例如重置样式、排版和其他全局样式。它们不应包含类或 ID。

html, body, ul, ol {
	margin: 0;
	padding: 0;
}
Layout#

布局规则控制页面上各个区块的组织方式如结构、排列。这些规则通常应用于主要组件,如headerfootermain section

/* Layout */
.l-container {}
.l-header {}
.l-footer {}
Module#

模块是应用程序的可复用的组件(如按钮、表单、卡片)。它们应相对独立,在整个项目中易于迁移和复用。

/* Module */ 
.m-button {}
.m-card {}
State#

状态规则用于修饰模块或布局,表示所修饰组件所处的状态(类似于 BEM 中的 Modifier)。

/* State */
.is-active {}
.has-error {}
Theme#

主题规则定义页面的不同主题或外观的样式。它们是可选的,仅当项目需要多个主题时才需要。

/* Theme */
.t-dark .m-button { }

下面是一个 SMACSS 的 HTML 示例:

<div class="l-container">
	<header class="l-header"><!-- content --></header>
	<main>
		<button class="m-button is-active">Click me</button>
	</main>
	<footer class="l-footer"><!-- content --></footer>
</div>

ITCSS#

ITCSS: Scalable and Maintainable CSS Architecture - Xfive
相比于 BEM 和 OOCSS,ITCSS(Inverted Triangle) 则没有那么高的使用率,但满意率则是比较高的(stateofcss 2019 / 2020)。但是跟 SMACSS 很像。

它根据 CSS 的特异性按特定顺序组织 CSS,将 CSS 分为七层,得到一个清晰的层次结构,防止冲突从而使代码更易于维护。
自下而上逐级增加抽象程度。

ITCSS 分为七层:

  1. 设置:全局配置和变量,例如颜色、字体和断点。
  2. 工具(Tools):整个项目中使用的混合和函数(在 SCSS 等预处理器中为 CSS 增加了 编程特性,从而实现更好的复用)。
  3. 通用:高层次、低特异性的样式,定位类似于用户代理样式表,提供一个默认值,抹平样式差异,如 resets 和 normalizations。
  4. 元素:HTML 元素的默认样式,例如titleaul
  5. 对象:与布局相关的样式,例如网格系统和容器。
  6. 组件:可重用的 UI 组件,例如按钮、卡片和表单。
  7. Utilities:场景特定但实用的辅助类,例如间距和可见性。

看起来会发现,ITCSSSMACSS 很像,elementsgeneric实际上就是 SMASS 的base类,settings对应theme类,objectcomponent则分别对应 SMASS 的layoutmodule
但是缺少state层的定位,多了utilities层。此外还有一个面向预处理器的Tools层。
可以视为对 SMACSS 的一种改进。

<style>
/* Settings */
:root {
--primary-color: #007bff;
--secondary-color: #6c757d;
--font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
/* Tools used with SCSS */
@mixin clearfix {
	&::after { 
		content: "";
		display: table;
		clear: both;
	}
}
/* Generic */
*, *::before, *::after {
	box-sizing: border-box;
}
/* Elements */
h1, h2, h3, h4, h5, h6 {
	font-family: var(--font-family-sans-serif);
	font-weight: bold;
}
/* Objects */
.o-container {
	max-width: 1200px;
	margin: 0 auto;
	padding: 0 15px;
}
/* Components */
.c-button {
	display: inline-block;
	padding: 10px 20px;
	background-color: var(--primary-color);
	color: #fff;
}
/* Utilities */
.u-text-center {
	text-align: center;
}
</style>

<div class="o-container">
	<header> <!-- ... --> </header>
	<main> <button class="c-button">Click me</button> </main>
	<footer> <!-- ... --> </footer>
</div>

Atomic CSS#

原子化 CSS(也称为函数式 CSS)越来越受欢迎,tailwind 大行其道。相比于上面各种组织 CSS 的范式,原子化 CSS 就如同一个逆子,反其道而行之。

通过为每个 CSS 属性创建一个 CSS 规则(进而创建唯一的类名),从而减少已定义规则总数的方法,从而实现大型样式重用。
比如对于background-color: red;这一个属性,可以创建一个 bg-red 的 css 类。除此之外,再无其他。
这样的好处是:

  • 使用到的 CSS 的属性是有限的,从而将项目的总 CSS 规则限制在一个可接受的范围内。(CSS 文件体积缩减)
  • 规避选择器的问题,将样式重新与 HTML 关联在一起,而不用担心选择器的命名、范围等一系列问题,查看 HTML 结构的同时也可以知道大致的样式
  • 简单、准入门槛低,更容易被不精通 CSS 的人理解。
    以 tailwind 首页的 example 为例,非常的暴力有效,在组件驱动开发场景下相对更契合。
<figure class="md:flex bg-slate-100 rounded-xl p-8 md:p-0 dark:bg-slate-800">
	<img class="w-24 h-24 md:w-48 md:h-auto md:rounded-none rounded-full mx-auto" src="/sarah-dayan.jpg" alt="" width="384" height="512">
	<div class="pt-6 md:p-8 text-center md:text-left space-y-4">
		<blockquote>
			<p class="text-lg font-medium">“Tailwind CSS is the only framework that I've seen scale on large teams. It’s easy to customize, adapts to any design, and the build size is tiny.”</p>
		</blockquote>
		<figcaption class="font-medium">
			<div class="text-sky-500 dark:text-sky-400"> Sarah Dayan </div>
			<div class="text-slate-700 dark:text-slate-500">
				Staff Engineer, Algolia
			</div>
		</figcaption>
	</div>
</figure>

小结#

了解完上面这些 “CSS 架构” 之后,不难发现,不同的 CSS 架构之间并不冲突,更多像是对同一问题在不同视角下提供的解决思路,对问题的不同划分,实际上可以相互配合。