介绍几个 CSS 方法论

在大型、复杂和快速迭代的系统中,CSS 将非常难以维护。原因之一就是 CSS 没有作用域的概念,每个 CSS 都是全局的,这意味着对 CSS 的任何修改就可能导致一些 UI 的级联改变。

CSS 的扩展语言 – CSS 预处理器,比如 Sass、Less 和 Stylus,使我们编写 CSS 更加容易,但在我看来,这些 CSS 的扩展语言并没有真正解决可扩展性问题。

在 CSS 支持作用域机制之前,我们需要一种机制,使我们的样式只与特定的 HTML 部分关联,这就是 CSS 方法论。本文将讨论如下的 CSS 方法论:

  • Object-Oriented CSS (OOCSS)
  • Block, Element, Modifier (BEM)
  • Scalable and Modular Architecture for CSS (SMACSS)
  • SUIT CSS
  • Systematic CSS

CSS 方法论是正式的,文档化的 CSS 编写方法,使我们能够将 CSS 作为一些小的独立的模块来开发和维护,而不是一个庞大的不可分割的一坨代码。采用一种 CSS 方法论 – 即便这是你自己创建的 – 它将使你在 Web 开发中更加得心应手。

相关文章:CSS Development at Large-Scale Websites

每个 CSS 方法论都为可扩展性和可维护性提供了一套解决方案,一个 CSS 方法论通常会定义:

  • CSS 和 HTML 的最佳实践
  • Class 和 ID 的命名约定
  • 有序的、分组的 CSS 样式
  • 代码格式

没有“最好”的 CSS 方法论,不同的个人/团队/项目适合于不同的方法论。希望通过本文你可以找到一个适合你的方法论,或者激发你创建一个你自己的。

Object-Oriented CSS (OOCSS)

Object-Oriented CSS 简称为 OOCSS,发布于 2009 年,这是第一个被广泛采用的 CSS 方法论,直到今天仍有很大的影响力。

OOCSS 主张结构与样式分离,明确区分内容和它的容器。在 OOCSS 中,样式规则使用相互独立的 CSS 选择器来界定。

案例分析

例如,一个按钮元素可以通过两个样式来设定:

  • .button – 提供按钮的基本样式
  • .grey-btn – 应用颜色和其他可视化属性

HTML

1
<button class="button grey-btn">

CSS

1
2
3
4
5
6
7
8
9
10
11
.button {
box-sizing: border-box;
height: 50px;
width: 100%;
}
.grey-btn {
background: #EEE;
border: 1px solid #DDD;
box-shadow: rgba(0, 0, 0, 0.5) 1px 1px 3px;
color: #555;
}

OOCSS 方法论的目标之一是减少样式规则中相同属性的重复率。该方法论试图通过许多小的、模块化的、专有功能的 CSS 类来实现这一目标。很少通过类型选择器(比如:h1,div 和 body)来指定其样式。

注意:在 OOCSS 方法论中,不鼓励使用后代选择器:

CSS

1
2
3
4
/* 在 OOCSS 中,不鼓励使用后代选择器 */
.wrapper .blog-post .button {
...
}

避免使用后代选择器,这样 HTML 的呈现就不依赖于特定的 HTML 结构(样式与结构分离)。

OOCSS 鼓励大家尽量复用已有样式,通过扩展现有的样式规则来创建新的样式类,而不要修改或覆盖已有的 CSS 属性。

下面我们想让无序列表的第一项的颜色突出一些,先看看不好的方式:

HTML

1
2
3
4
5
6
<!-- 反例 -->
<ul class="to-do">
<li>Combine my CSS files</li>
<li>Run CSS Lint</li>
<li>Minify my stylesheet</li>
</ul>

CSS

1
2
3
4
5
6
7
8
/* 反例 */
.to-do {
color: #FFF;
background-color: #000;
}
.to-do li:first-child {
color: #FF0000;
}

为了使我们的 CSS 更加模块化和更具可维护性,并且避免使用后代选择器,看下面更好的写法:

HTML

1
2
3
4
5
6
<!-- OOCSS -->
<ul class="to-do">
<li class="first-to-do-item">Combine my CSS files</li>
<li>Run CSS Lint</li>
<li>Minify my stylesheet</li>
</ul>

CSS

1
2
3
4
5
6
7
8
/* OOCSS */
.to-do {
color: #FFF;
background-color: #000;
}
.first-to-do-item {
color: #FF0000;
}

小结

OOCSS 的主要缺陷是将产生大量的 CSS 类,这将非常难以维护。在我看来,面向对象编程的理念并不是非常适合 CSS。但这并不是说 OOCSS 的原则不实用,相反,OOCSS 是一个最基础的方法论,它给大规模的 CSS 带来了福音。

Block, Element, Modifier (BEM)

Block, Element, Modifier 通常被称为 BEM,由俄罗斯的 Google 团队设计。BEM 的主要设计理念是用不同的角色来区分不同的 CSS 类,也就是说用角色来命名 CSS 类。BEM 补充了 OOCSS 的不足,因为 OOCSS 并没有任何的命名约定。

在 BEM 方法论中,block 是一个独立的模块化的 UI 组件,一个 block 可以由多个 HTML 元素组成,甚至可以由多个 block 组成,例如导航菜单或搜索表单。element 是 block 的组成部分,服务于一个单一的目的,例如,在一个导航中,element 就是导航菜单中的链接,在实际中就是一个 li 元素和一个 a 元素。modifier 用于改变 block 和 element 的默认样式。

下面是 BEM 的命名规范:

  • .block
  • .block–modifier
  • .block__element
  • .block__element–modifier

案例分析

看下面的登录表单:

1
2
3
4
5
<form>
<label>Username <input type="text" name="username" /></label>
<label>Password <input type="password" name="password" /></label>
<button>Sign in</button>
</form>

用 BEM 方法论将如下实现:

1
2
3
4
5
6
7
8
9
10
11
<form class="loginform loginform--errors">
<label class="loginform__username loginform__username--error">
Username <input type="text" name="username" />
</label>
<label class="loginform__password">
Password <input type="password" name="password" />
</label>
<button class="loginform__btn loginform__btn--inactive">
Sign in
</button>
</form>

.loginform 类就是 block。

.loginform 由下面三个 element 组成

  • .loginform__username 用户名
  • .loginform__password 密码
  • .loginform__btn 登录按钮

还有三个 modifier 分别是:

  • .loginform__username--error 使用户名元素呈现为一个错误提醒的样式
  • .loginform__btn--inactive 使登录按钮元素呈现为不可用的样式
  • .loginform--errors 使整个登录表单呈现为错误提醒的样式

BEM 的命名约定有助于 CSS 作者遵循 OOCSS 的扁平化设计原则,避免了使用深层次的后代选择器。例如下面的 CSS 选择器:

1
2
3
.loginform .username .error {
...
}

可以使用单一一个 CSS 类来实现:

1
2
3
.loginform__username--error {
...
}

小结

BEM 是一个非常健壮的命名约定,可以方便地区分不同 CSS 类的目的,而且在 HTML 中也可以很方便地识别 CSS 类之间的关系。

BEM 的反对者主要有以下两类:

  • CSS 类名太长太丑
  • 命名约定对不熟练的开发者不友好

Scalable and Modular Architecture for CSS (SMACSS)

Jonathan Snook 在 2011 年出版了《Scalable and Modular Architecture for CSS》这本书,简称为 SMACSS,发音是 [smacks]。

该方法论的核心思路是如何为 CSS 样式分类。Snook 提出了一下五类:

  • Base 为单个 HTML 元素设置默认样式,通常是标签选择器:
1
2
3
4
5
6
7
8
9
h1 {
font-size: 32px;
}
div {
margin: 0 auto;
}
a {
color: blue;
}
  • Layout 与网页布局相关的样式,样式名通常以 layout-l- 开头:
1
2
3
4
5
6
.layout-sidebar {
width: 320px;
}
.l-comments {
width: 640px;
}
  • Modules 模块和可复用的组件:
1
2
3
4
5
6
7
8
.call-to-action-button {
text-transform: uppercase;
color: #FFF200;
}
.search-form {
display: inline-block;
background-color: E1E1E1;
}
  • State 指定界面特定状态的样式规则:
1
2
3
4
5
6
7
8
.is-hidden {
display: none;
}
.is-highlighted {
color: #FF0000;
background-color: #F4F0BB;
border: 1px solid #CBBD15;
}
  • Themes 影响整体布局和模块的样式,由用户设置触发。

SMACSS 提供了一个比 BEM 更简单的命名约定。基础样式(base)没有 CSS 类名,因为他们都是标签选择器(h1, p, a 等),模块(module)有一个唯一的样式名,子模块用父模块名作为前缀。

看下面布局,在 .l-footer 中,有一个搜索模块,并且搜索表单至少被用户提交过一次:

1
2
3
4
5
6
<section class="l-footer">
<form class="search is-submitted">
<input type="search" />
<input type="button" value="Search">
</form>
</section>

SMACSS 也不推荐使用后代选择器,Jonathan Snook 还介绍了 CSS 的最佳实用深度理论,该理论的核心思想是通过类名来精确控制目标元素的样式,从而减少 HTML 结构对样式的影响。

SUIT CSS

Nicolas Gallagher 提出的 SUIT CSS 发布于 2014年,他通过 CSS 预处理器定义了一套类似 BEM 命名规范,SUIT CSS 的命名方式有如下五种:

  • u-utilityName
  • ComponentName
  • ComponentName–modifierName
  • ComponentName-elementName
  • ComponentName.is-stateOfName

该命名约定突出的区分了:

  • General utility classes
  • Standalone/modular UI components
  • Individual elements
  • Modifiers

看下面的登录表单:

1
2
3
4
5
6
7
8
9
<form class="LoginForm LoginForm--errors">
<label class="LoginForm-username is-required">
Username <input type="text" name="username" />
</label>
<label class="LoginForm-password">
Password <input type="password" name="password" />
</label>
<button class="LoginForm-button is-inactive">Sign in</button>
</form>

Systematic CSS

Systematic CSS 是原文作者最近才提出来的一个方法论,借鉴了许多 OOCSS、BEM、SMACSS、SUIT CSS 和其他一些 CSS 方法论的原理和思路。Systematic CSS 可以作为现有 CSS 方法论的简单的替代方案,只需要记住少许几个命名约定,并且命名也更加直观。

在 Systematic CSS 方法论中,一个页面被分为一下四个部分:

  • Layout
  • Elements
  • Widgets
  • Modifiers

首先,使用 sectiondiv 元素来创建一个页面布局:

1
2
3
4
5
6
7
8
9
10
11
<div class="CONTAINER">
<header class="BANNER"></header>
<nav class="NAVIGATION_PRIMARY"></nav>
<nav class="NAVIGATION_SECONDARY"></nav>
<main class="MAIN"></main>
<aside class="ADVERTS"></aside>
<footer class="FOOTER">
<nav class="SITEMAP"></nav>
<div class="LEGAL"></div>
</footer>
</div>

其次,为内容和交互元素建立默认样式,比如,标题(h1, h2, h3)、段落(p)、列表(ulol)、表格(table)、表单(form)等。

然后,确定页面中那些重复的部分,将这些重复的部分提取为一个个独立的模块。在 Systematic CSS 中这些模块被称为 widget。看下面两个 widget:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- navigation bar -->
<div class="NavBar">
<ul>
<li><a href="./">Home</a></li>
<li><a href="about.html">About</a></li>
<li><a href="learn/">Learn</a></li>
<li><a href="extend/">Extend</a></li>
<li><a href="share/">Share</a></li>
</ul>
</div>
<!-- search form -->
<div class="SearchBox">
<form action="search.html" method="get">
<label for="input-search">Search</label>
<input name="q" type="search" id="input-search" />
<button type="submit">Search</button>
</form>
</div>

最后,为一些默认样式添加修饰(modifier)类。

看下面例子,navbar-primary 修饰类改变了 NavBar 的默认样式,navbar-selected 修饰类标记了当前的选中项:

1
2
3
4
5
6
7
8
9
<div class="NavBar navbar-primary">
<ul>
<li><a href="./">Home</a></li>
<li><a href="about.html" class="navbar-selected">About</a></li>
<li><a href="learn/">Learn</a></li>
<li><a href="extend/">Extend</a></li>
<li><a href="share/">Share</a></li>
</ul>
</div>

在 Systematic CSS 中,一个 CSS 类可以是:

  • 布局(layout)名称
  • 组件(widget)名称
  • 修饰符

一个 CSS 类只能是以上三种的一种,不能将以上三种结合使用。它们分别有不同的命名约定:

  • Layout - 全大写
  • Widget - 大驼峰
  • Modifier - 全小写加连接符

这个命名约定的优点是,类的层次结构由它们的名字来代表。

布局的类名采用全大写的形式,非常显眼,例如:.NAVIGATION.SIDEBAR.FOOTER

组件的类名采用大驼峰的形式,比较显眼,例如:.MainMenu.ImageGrid.BlogPost

修饰类是最不重要的类,因为它们修饰一些默认样式,不是默认样式的必要部分,所以它们采用全小写的形式,也最不显眼,例如:.is-highlighted.has-errors.hidden

其他 CSS 方法论

相关文章

go2top