本文在概述的基础上,进一步介绍 BEM、OOCSS、ITCSS 这些用于组织 pureCSS 的范式,也即是 CSS 架构。
尽管习惯上这几种范式一般会与 SCSS/LESS 等预处理器结合使用,但是在概念上,实际上是一种组织 CSS,避免 CSS 变得混乱的方法论(CSS 架构)。SCSS/LESS 更像是高效书写标准 CSS 的方言。
OOCSS#
面向对象的 CSS,推崇结构与样式分离,容器与内容分离。咋一看,这句话非常抽象,还是结合场景来看看。
结构与样式分离#
比如一个 Login 页面,有两个按钮,login-button
和register-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
这个选择器,p
与blog-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)也是一种范式,提倡有意识区分的Block
,Element
以及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#
布局规则控制页面上各个区块的组织方式如结构、排列。这些规则通常应用于主要组件,如header
、footer
和main 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 分为七层:
- 设置:全局配置和变量,例如颜色、字体和断点。
- 工具(Tools):整个项目中使用的混合和函数(在 SCSS 等预处理器中为 CSS 增加了 编程特性,从而实现更好的复用)。
- 通用:高层次、低特异性的样式,定位类似于用户代理样式表,提供一个默认值,抹平样式差异,如 resets 和 normalizations。
- 元素:HTML 元素的默认样式,例如
title
、a
和ul
等 - 对象:与布局相关的样式,例如网格系统和容器。
- 组件:可重用的 UI 组件,例如按钮、卡片和表单。
- Utilities:场景特定但实用的辅助类,例如间距和可见性。
看起来会发现,ITCSS
跟 SMACSS
很像,elements
和generic
实际上就是 SMASS 的base
类,settings
对应theme
类,object
和component
则分别对应 SMASS 的layout
和 module
。
但是缺少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 架构之间并不冲突,更多像是对同一问题在不同视角下提供的解决思路,对问题的不同划分,实际上可以相互配合。