【译】编写整洁 CSS 代码的黄金法则

要编写整洁的 CSS 代码,你必须准守以下规则,它将有助于你写出轻量的可复用的 CSS 代码:

  • 避免使用全局选择器和元素选择器
  • 避免使用权重过高的选择器
  • 使用语义化类名
  • 避免 CSS 和标签结构的紧耦合

本文将依次阐述上述规则。

避免使用全局选择器

全局选择器包括通配选择器(*)、元素选择器(例如 p、button、h1 等)和属性选择器(例如[type=checkbox]),这些选择器的样式声明会被应用到全站所有符合要求的元素上,例如:

button {
  background: #ffc107;
  border: 1px outset #ff9800;
  display: block;
  font: bold 16px / 1.5 sans-serif;
  margin: 1rem auto;
  width: 50%;
  padding: 0.5rem;
}

这段代码看起来没什么问题,但是倘若我们需要新建一个样式不同的 button 元素选择器呢?我们需要定义一个类名为 close 的 button 元素用于关闭对话框组件:

<section class="dialog">
  <button type="button" class="close">Close</button>
</section>

PS: 为什么不使用 dialog 元素?
我们这里使用 section 元素而不使用 dialog 元素是因为只有基于 Blink 内核的浏览器才支持 dialog 元素, 例如 Chrome/Chromium、Opera、和 Yandex 等。

现在,需要编写 CSS 代码来覆盖那些不需要继承于 button 元素选择器的属性:

.close {
  background: #e00;
  border: 2px solid #fff;
  color: #fff;
  display: inline-block;
  margin: 0;
  font-size: 12px;
  font-weight: normal;
  line-height: 1;
  padding: 5px;
  border-radius: 100px;
  width: auto;
}

我们仍然需要许多类似的样式来覆盖浏览器的默认样式。但如果我们将 button 元素选择器的样式用一个 default 类选择器来替换会如何呢?结果发现 close 类中不需要指定 display、font-weight、 line-height、margin、 padding 和 width 等属性,这便减少了 23% 的代码量:

.default {
  background: #ffc107;
  border: 1px outset #ff9800;
  display: block;
  font: bold 16px / 1.5 sans-serif;
  margin: 1rem auto;
  width: 50%;
  padding: 0.5rem;
}

.close {
  background: #e00;
  border: 2px solid #fff;
  color: #fff;
  font-size: 12px;
  padding: 5px;
  border-radius: 100px;
}

还有一点非常重要:避免使用全局选择器将有助于减少样式冲突,即在某个模块或页面的开发中添加样式不会对其他模块或页面造成影响

全局样式和选择器非常适合用于重置和统一浏览器默认样式,在其他情况下,它们只会造成代码臃肿。

避免使用权重过高的选择器

保持选择器的低权重是编写轻量级、可复用和可维护的 CSS 代码的又一关键所在。正如你所回忆起的权重,一个元素选择器的权重是 0,0,1,而类选择器的权重则是 0,1,0:

/* Specificity of 0,0,1 */
p {
  color: #222;
  font-size: 12px;
}
/* Specificity of 0,1,0 */
.error {
  color: #a00;
}

当你为一个元素添加类名后,这个元素选择器的的优先级将高于一般元素选择器。这里没有必要将类选择器和属性选择器组合在一起来提升优先级,这样做只会增加选择器的权重和文件的大小。

换句话说,没有必要使用 p.error 这样的选择器,因为仅仅一个 .error 就能达到同样的效果,此外还有一个好处是 .error 还可以被其他元素所复用,而 p.error 选择器则会将 .error 这个类限制于 p 元素上。

避免链式类选择器

同时还需要避免链式类选择器。比如:.message.warning 这样的选择器的权重为 0,2,0。权重越高意味着越难进行样式覆盖,增长连接都会导致这种副作用,举例如下:

.message {
  background: #eee;
  border: 2px solid #333;
  border-radius: 1em;
  padding: 1em;
}
.message.error {
  background: #f30;
  color: #fff;
}
.error {
  background: #ff0;
  border-color: #fc0;
}

如图所示,在上述 CSS 作用下<p class="message">会呈现一个带有深灰色边框和灰色背景的盒子。

A message

而`

`会呈现 .message.error 类的背景和 .error 类的边框:

A error occured

要想覆盖链式类选择器的样式,只能使用权重更高的选择器。为了消除黄色的边框,需要在已有选择器上再加一个类名或一个标签选择器:`.message.warning.exception` 或 `div.message.warning`,更好的做法是创建一个新类。如果你发现你正在使用链式类选择器,那就该回过头重新考虑了。要么是设计上存在不合理的地方,要么就是过早的使用链式类从而想避免那些尚不存在的问题。解决这些问题将会带来更高的可维护性和可复用性。

避免使用 id 选择器

因为在一篇文档中每个 id 只能对应一个元素,所以 id 选择器的 CSS 规则是很难复用的。这样做一般都会涉及到一系列的 id 选择器,例如#sidebar-features#sidebar-sports

id 标识符是具有很高的权重的,要想覆盖它就必须使用更“长”的选择器。例如下面这段 CSS 代码,为了覆盖 #sidebar 的背景颜色属性,必须使用 #sidebar.sports#sidebar.local

#sidebar {
  float: right;
  width: 25%;
  background: #eee;
}

#sidebar.sports {
  background: #d5e3ff;
}

#sidebar.local {
  background: #ffcccc;
}

改用类选择器,例如 .sidebar,就可以简化我们的 CSS 选择器:

.sidebar {
  float: right;
  width: 25%;
  background: #eee;
}

.sports {
  background: #d5e3ff;
}

.local {
  background: #ffcccc;
}

这不仅能减少字节,而且.sport.local类还能添加到其他元素上。

使用属性选择器例如[id=sidebar]可以解决 id 选择器高权重的问题,尽管其复用性不如类选择器,但其低权重可以让我们避免使用链式类选择器。

PS id 选择器的高权重也确有用武之地
在某些情况下,你可能确实需要 id 选择器的高权重性。例如,一些网络上的媒体站点可能需要其所有子站都使用同样的导航条组件,该组件必须在所有站点都表现一致并且其样式是难以被覆盖的。此时,使用 id 选择器就可以减少导航条样式被意外覆盖的情况。

最后,让我们再来讨论一下形如 #main article.sports table#stats tr:nth-child(even) td:last-child 这样的选择器。不仅它长的离谱,而且其权重为 2,3,4,也很难复用。在你的 HTML 标记文档中又有多少标签真能匹配这一选择器呢?我们稍作修改,可以将选择器拆分为#stats tr:nth-child(even) td:last-child,其权重也足够满足需求了。当然最好的办法是使用类选择器,这样既能提高复用性又能减少代码量。

PS:预处理器嵌套综合症
权重过高的选择器大多是由预处理器中过多的嵌套造成的

使用语义化类名

所谓的语义化,是指要有意义,类名应该表明这是什么规则或者这些规则影响到那些内容。此外类名也要能够适应 UI 需求的变化。命名看似简单,实则不然。

不要使用.red-text.blue-button .border-4px.margin10px 这样的类名,因为这些类名和当前的设计耦合的太紧密。用class="red-text"来标记一个错误的信息,看似可行,但如果设计稿发生了变化并要求将错误信息用橙底黑字表示呢?这时原有类名就不准确了,使得你和你的同事更难以理解代码的真正含义。

在这种情况下,比较好的做法是用.alert, .error, 或者 .message-error等类名。这些类名指明了这些类如何使用以及他们会影响到哪些内容。对于定义页面布局的类名,不妨加上 layout-grid-col-l- 等前缀,使人一眼可以看出它们的作用。之后关于 BEM 方法论的章节会详细阐述了这一过程。

避免 CSS 和标签结构的紧耦合

你可能在代码中使用过子元素选择器和后代选择器。子元素选择器形如 E > F,其中 F 是某个元素,而 E 是 F 的直接父元素。例如,article > h1 会影响 <article><h1>Advanced CSS</h1></article> 中的 h1 元素,但不会影响 <article><section><h1>Advanced CSS</h1></section></article> 中的 h1 元素。另一方面,后代选择器形如 E F,其中 F 是某个元素而 E 是 F 的祖先元素。还用上述例子,则那两种标签结构中的 h1 元素都会受到 article h1 的影响。

不是说子元素选择器和后代元素选择器的继承性不好,实际上它们在限制 CSS 规则的作用域方面确实发挥着很好的作用。但它们也绝非理想之选,因为标签结构经常会发生改变。

你可能会有以下经历,你为某个客户编写了一些模版,并且在 CSS 代码中用到了子元素选择器和后代选择器,并且大多数都是元素选择器,即形如 .promo > h2.media h3 这样的选择器;后来你的客户又聘请了一位 SEO 技术顾问,他检查了你代码中的标签结构并建议你将 h2 和 h3 分别改为 h1 和 h2,这时候问题来了 —— 你必须同时修改 CSS 代码。

这个时候类选择器再一次表现出其优点。使用 .promo > .headline .media .title (或者更简单一些: .promo-headline.media-title)使得在改变标签结构的时候无需改变 CSS 代码。

当然,这条规则假设你对标签结构有足够的控制权,当在面对一些遗留的 CMS 系统的时候这些可能是不正确的,在这种情况下使用子元素选择器、后代选择器和伪类选择器是适当的同时也是必要的。

**PS:**更多架构合理的 CSS 规则
Philip Walton 在其 “CSS 架构”一文中讨论了相关规则,有关 CSS 架构的更多想法参见 Roberts 的网站 CSS 原则 以及 Nicolas Gallagher 的博客文章 HTML 语义化及前端架构

接下来将会探讨有关 CSS 架构的两种方法,这两种方法主要用于提升大规模团队和大规模站点的开发效率,但对于小团队来说其实也是十分适用的。


译文链接:编写整洁 CSS 代码的黄金法则
原文链接:Golden Guidelines for Writing Clean CSS
翻译作者:Jesse
转载必须保留译文链接、原文链接、翻译作者等信息。