模块化 CSS( Modular CSS)是指把页面分割成不同的组成部分,这些组成部分可以在多种上下文中重复使用,并且互相之间没有依赖关系。最终目的是,当我们修改其中一部分 CSS 时,不会对其他部分产生意料之外的影响。
之前的样式表可以使用选择器在页面上随意修改,模块化的样式则允许开发人员添加一些限制。我们把样式表的每个组成部分称为模块( module),每个模块独立负责自己的样式,不会影响其他模块内的样式。也就是说,在 CSS 里引入了软件封装的原则。

封装( encapsulation) ——相关的函数和数据集合在一起组成对象,通常用来隐藏结构化对象内部的状态或值,从而使外部因素不能操作对象内部。

CSS 中没有数据和传统函数的概念,但是有选择器及其命中的页面元素。为了达到封装的目的,这些会成为模块的组成部分,并且每个模块都只负责少量的 DOM 元素的样式。
有了封装的思想,我们就可以为页面上那些彼此分立的组件定义模块了,像导航菜单、对话框、进度条、缩略图,等等。可以通过为 DOM 元素设置一个独一无二的的类名来识别每个模块。同时,每个模块包含一系列子元素,构建成页面上的组件。模块内部可以嵌套其他模块,最终构成完整的页面。

1. 基础样式:打好基础

开始写模块化样式之前,需要先配置好环境。每个样式表的开头都要写一些给整个页面使用的通用规则,模块化 CSS 也不例外。这些规则通常被称为基础样式,其他的样式是构建在这些基础样式之上的。基础样式本身并不是模块化的,但它会为后面编写模块化样式打好基础。
新建一个网页和一个样式表,把代码清单1中的基础样式粘贴到 CSS 中。这里只是列举了你可能用到的一些基础样式。

  1. /*重置盒模型大小*/
  2. : root {
  3. box-sizing: border-box;
  4. }
  5. *, *::before, *::after {
  6. box-sizing: inherit;
  7. }
  8. body {
  9. font-family: 'Times New Roman', Times, serif;
  10. }

其他常用的基础样式还包括链接的颜色、标题的样式、外边距等。 标签默认的外边距很小,你可能会考虑将它的外边距去掉。根据项目的实际情况,你也可能想为表单字段、表格和列表等添加一些样式。
基础样式应该是通用的,只添加那些影响页面上大部分或者全部内容的样式。选择器不应该使用类名或者 ID 来匹配元素,应只用标签类型或者偶尔用用伪类选择器。核心思想是这些基础样式提供了一些默认的渲染效果,但是之后可以很方便地根据需要覆盖基础样式。基础样式配置完成以后,很少会再修改。我们会在基础样式的稳定表现之上,构建模块化CSS。在样式表中,基础样式后面的内容将主要由各种模块组成。

2. 一个简单的模块

下面来创建一个短消息通知的模块。每个模块都需要一个独一无二的名称,我们把这个模块叫作“message”。为了吸引用户的注意,可以加上一些颜色和边框效果(如下图所示)。
image.png
这个模块使用一个类名为 message 的 div 作为标记。将代码清单2添加到网页中。

  1. <div class="message">
  2. Save successful
  3. </div>

模块的 CSS 是一个规则集,通过类名指向模块。 CSS 中设置了内边距、边框、边框圆角和颜色。把代码清单3添加到样式表中,放在基础样式后面,就可以把这些样式应用到消息模块了。

  1. .message {
  2. padding: 0.8em 1.2em;
  3. border-radius: 0.2em;
  4. border: 1px solid #265559;
  5. color: #265559;
  6. background-color: #e0f0f2;
  7. }

模块的选择器由单个类名构成,这非常重要。选择器里没有其他规则来约束这些样式仅作用在页面上的某个地方。对比一下,如果使用一个类似于#sidebar .message 的选择器,就意味着这个模块只能用在#sidebar 元素内部。没有这些约束,模块就可以在任意上下文中重复使用。
通过给元素添加类名,就可以把这些样式复用到很多场景,比如针对表单输入给用户反馈,提供醒目的帮助文字,或者提醒用户注意免责声明条款等。使用相同的组件,就产生了一套风格一致的 UI。所有用到组件的地方将看上去一样,不会出现有的地方蓝绿色有色差、有的地方内边距偏大等问题。

2.1. 模块的变体

保持一致性确实不错,但有时候需要特意避免一致。上面的消息模块很好用,但某些情况下我们需要它看起来有些不同。比如,我们需要显示一条报错的消息,这时候应该使用红色而不是之前的蓝绿色。再比如,我们可能想要区分传递信息的消息和表示操作成功的通知(比如保存成功)。这可以通过定义修饰符( modifiers)来实现。
通过定义一个以模块名称开头的新类名来创建一个修饰符。例如,消息模块的 error 修饰符应该叫作 message-error。通过包含模块名称,可以清楚地表明这个类属于消息模块。

说明: 常用的写法是使用两个连字符来表示修饰符,比如 message—error。

下面我们为模块创建三个修饰符:成功、警告和错误。将代码清单4添加到样式表中。

  1. .message {
  2. padding: 0.8em 1.2em;
  3. border-radius: 0.2em;
  4. border: 1px solid #265559;
  5. color: #265559;
  6. background-color: #e0f0f2;
  7. }
  8. .message--success {
  9. color: #2f5926;
  10. border-color: #2f5926;
  11. background-color: #cfe8c9;
  12. }
  13. .message--warning {
  14. color: #594826;
  15. border-color: #594826;
  16. background-color: #e8dec9;
  17. }
  18. .message--error {
  19. color: #59262f;
  20. border-color: #59262f;
  21. background-color: #e8c9cf;
  22. }

修饰符的样式不需要重新定义整个模块,只需覆盖要改变的部分。在本例中,这意味着只需要修改文本、边框和背景的颜色。
如代码清单5所示,把主模块类名和修饰符类名同时添加到元素上,就可以使用修饰符了。这样既应用了模块的默认样式,又可以在有需要的时候利用修饰符重写部分样式。

  1. <div class="message message--error">
  2. Invalid password
  3. </div>

同样,有需要时也可以使用成功或警告修饰符。这些修饰符只是改变了模块的颜色,但其他的修饰符可能会改变模块的大小甚至布局。

1. 按钮模块的变体

下面创建另一个带有一些变体的模块。我们将实现一个按钮模块,其中包含大小和颜色选项的变体(如下图所示)。我们可以用不同的颜色为按钮添加视觉意义。绿色代表积极的行为,比如保存和提交表单;红色意味着警告,有利于防止用户不小心点击取消按钮。
image.png
代码清单6给出了这些按钮的样式,包括基础按钮模块和四个修饰符类:两个尺寸修饰符和两个颜色修饰符。将这些代码添加到样式表中。

  1. .button {
  2. padding: 0.5em 0.8em;
  3. border: 1px solid #265559;
  4. border-radius: 0.2em;
  5. background-color: transparent;
  6. font-size: 1rem;
  7. }
  8. .button--success {
  9. border-color: #cfe8c9;
  10. color: #fff;
  11. background-color: #2f5926;
  12. }
  13. .button--danger {
  14. border-color: #e8c9c9;
  15. color: #fff;
  16. background-color: #a92323;
  17. }
  18. .button--small {
  19. font-size: 0.8rem;
  20. }
  21. .button--large {
  22. font-size: 1.2rem;
  23. }

尺寸修饰符能够设置字体的大小。通过更改字号来调整元素相对单位 em 的大小,进而改变内边距和边框圆角的大小,而不需要重写已经定义好的值。

提示: 要把一个模块所有的代码集中放在同一个地方,这样一个接一个的模块就会组成我们最终的样式表。

有了这些修饰符,写 HTML 的时候就有了多种选择。我们可以根据按钮的重要程度来添加修饰符类,修改按钮的大小,也可以选择用不同的颜色来为用户提供语境意义。
代码清单7里的 HTML 组合使用修饰符来创建多个按钮。将这些代码添加到页面,并查看实际效果。

  1. <button class="button button--large">Read more</button>
  2. <button class="button button--success">Save</button>
  3. <button class="button button--danger button—small">Cancel</button>

双连字符的写法可能看起来有点儿多余,但当我们开始创建名称很长的模块的时候,比如导航菜单或者文章摘要,好处就显现出来了。为这些模块添加修饰符后,类名将如 nav-menu—horizontal 或者 pull-quote—dark。
双连字符的写法很容易区分哪部分是模块名称,哪部分是修饰符。 nav-menu—horizontal和 nav—menu-horizontal 分别代表了不同的含义。这样一来,即使项目里有很多名称相似的模块,也很容易分辨它们。

2. 不要使用依赖语境的选择器

假设我们正在维护一个网站,里面有浅色调的下拉菜单。有一天老板说,网页头部的下拉菜单需要改成带白色文本的深色调。
如果没有模块化 CSS,我们可能会使用类似于.page-header .dropdown 的选择器,先选中要修改的下拉菜单,然后通过选择器写一些样式,覆盖 dropdown 类提供的默认颜色。现在要写模块化 CSS,这样的选择器是严格禁用的。虽然使用后代选择器可以满足当下的需要,但接下来可能会带来很多问题。下面我们来分析一下。
第一,我们必须考虑把这段代码放在哪里,是和网页头部的样式放在一起,还是跟下拉菜单的样式放在一起?如果我们添加太多类似的单一目的的规则,样式之间毫无关联,到最后样式表会变得杂乱无章。并且,如果后面需要修改样式,你还能想起来它们放在哪里吗?
第二,这种做法提升了选择器优先级。当下次需要修改代码的时候,我们需要满足或者继续提升优先级。
第三,后面我们可能需要在其他场景用到深色的下拉列表。刚才创建的这个下拉列表是限定在网页头部使用的。如果侧边栏也需要同样的下拉列表,我们就得为该规则集添加新的选择器来匹配两个场景,或者完整地复制一遍样式。
第四,重复使用这种写法会产生越来越长的选择器,将 CSS 跟特定的 HTML 结构绑定在一起。例如,如果有个#products-page .sidebar .social-media div:first-child h3这样的选择器,样式集就会和指定页面的指定位置紧紧耦合。这些问题是开发人员处理 CSS 的时候遭受挫折的根源。使用和维护的样式表越长,情况越糟。新样式需要覆盖旧样式时,选择器优先级会持续提升。到后面不知不觉地就会发现,我们写了一个选择器,其中包含两个 ID 和五个类名,只是为了匹配一个复选框。
在样式表中,元素被各种彼此不相关的选择器匹配,这样很难找到它使用的样式。理解整个样式表的组织方式变得越来越困难,你搞不明白它是怎样把页面渲染成这样的。搞不懂代码就意味着 bug 变得常见,可能很小的改动就会弄乱大片的样式。删除旧代码也不安全,因为你不了解这段代码是干什么的,是否还在用。样式表越长,问题就愈发严重。模块化 CSS 就是要尝试解决这些问题。
当模块需要有不同的外观或者表现的时候,就创建一个可以直接应用到指定元素的修饰符类。比如,写.dropdown—dark,而不是写成.page-header .dropdown。通过这种方式,模块本身,并且只能是它本身,可以决定自己的样式表现。其他模块不能进入别的模块内部去修改它。这样一来,深色下拉列表并没有绑定到深层嵌套的 HTML 结构上,也就可以在页面上需要的地方随意使用。
千万不要使用基于页面位置的后代选择器来修改模块。坚决遵守这个原则,就可以有效防止样式表变成一堆难以维护的代码。

2.2. 多元素模块

我们已经创建了消息和按钮两个模块,简单又好用,它们都由单个元素组成,但是有很多模块需要多个元素。我们不可能只靠一个元素就实现下拉菜单或者模态框。
下面来创建一个更复杂的模块。这是一个媒体对象(如下图所示)。
image.png
这个模块由四个元素组成: div 容器、容器包含的一张图片和正文、正文里的标题。跟其他模块一样,我们会给主容器添加 media 类名来匹配模块名称。对于图片和正文,可以使用类名mediaimage 和 mediabody。这些类名以模块名称开头,后跟双下划线,然后是子元素的名称。(这是 BEM 命名规范里的另一种约定。)就跟双连字符代表的修饰符一样,这样的类名可以清楚地告诉我们这个元素扮演了什么角色、属于哪个模块。
媒体模块的样式如代码清单8所示,将其添加到样式表中。

  1. /*主容器*/
  2. .media {
  3. padding: 1.5em;
  4. background-color: #eee;
  5. border-radius: 0.5em;
  6. }
  7. /*清除浮动*/
  8. .media::after {
  9. content: "";
  10. display: block;
  11. clear: both;
  12. }
  13. /*图片和正文子元素*/
  14. .media__image {
  15. float: left;
  16. margin-right: 1.5em;
  17. }
  18. .media__body {
  19. overflow: auto;
  20. margin-top: 0;
  21. }
  22. /*正文里的标题*/
  23. .media__body > h4 {
  24. margin-top: 0;
  25. }

你会发现并不需要使用很多后代选择器。图片是媒体模块的一个子元素,所以可以使用选择器.media > .mediaimage,但这不是必要的。因为 mediaimage 类名包含了模块的名称,所以已经确保模块名称是独一无二的了。
正文标题确实直接使用了后代选择器。其实也可以用 mediatitle 类(或者 mediabody__title,这样可以完整地表示出在整个层级中的位置),但是大部分时候没必要。在本例中,

标签已经足够语义化,能够表明这是媒体模块的标题。不过这样一来,标题就不能使用其他的 HTML 标签(

或者

)了。如果你不太喜欢这么严格的限制,可以改成使用类名来匹配元素。
将代码清单9中的模块标记添加到页面中。

  1. <div class="media">
  2. <img src="./image/runner.png" alt="" class="media__image">
  3. <div class="media__body">
  4. <h4>Strength</h4>
  5. <p>
  6. Strength training is an important part of
  7. injury prevention. Focus on your core&mdash;
  8. especially your abs and glutes.
  9. </p>
  10. </div>
  11. </div>

这是个多功能的模块,可以工作在各种尺寸的容器内部,随着容器宽度自适应调整。正文可以包含多个段落,也可以使用不同尺寸的图片(可以考虑为图片添加 max-width 属性,防止图片挤出正文区域)。

1. 同时使用变体和子元素

我们也可以创建模块的变体。现在可以很轻松地把图片从左浮动改成右浮动(如下图所示)。
image.png
变体 media—right 可以实现这样的效果。我们把变体的类名添加到模块的主 div 上( ),然后通过类名匹配图片并设置为右浮动。
将修饰符类名添加到 HTML 里的元素上,然后把代码清单10添加到样式表里查看效果。

  1. .media--right > .media__image {
  2. float: right;
  3. }

这条规则覆盖了媒体图片之前的 float: left。由于浮动的工作原理,我们不需要改变 HTML中元素的排列顺序。

2. 避免在模块选择器中使用通用标签名

我们在媒体模块中使用了选择器.mediabody > h4 来匹配标题元素。这么做是允许的,因为

标签就是用来标识一个次要标题的。同样的方式也可以用在带列表的模块上。相比为列表里的每个项目都添加 menuitem 类名,使用.menu > li 匹配菜单项简单多了,尽管这种写法有些争议。
我们应该避免使用基于通用标签类型的匹配,比如 div 和 span。类似于.page-header >span 的选择器太宽泛了。最初建立模块的时候,可能只是用 span 标签做一件事,但谁也说不准以后会不会出于其他目的再添加第二个 span。后面再为 span 追加类名就比较麻烦了,因为我们需要在 HTML 标记中找到所有用到模块的地方,全部改一遍。

3. 把模块组合成更大的结构

每个模块应该只做一件事情。消息模块的职责是使消息提示醒目;媒体模块的职责是在一段文本中配置一张图片。我们可以简洁明了地概括出它们的目标。有的模块是为了版面布局,有的是为了编写体例。当模块想要完成不止一件事的时候,我们应该考虑把它拆分成更小的模块。
创建模块之前应该先自问一下:“从更高的层面上看,这个模块的职责是什么?”对于本例,你的回答可能是这样的:“用按钮触发下拉菜单并展示上下堆叠排列的菜单项。”
就这个场景来说,这还算是个比较恰当的描述。但是我有一条经验:“如果你不得不使用并(或者和)这个词来表述模块的职责,那你可能正在描述多项职责。”因此,模块究竟是要触发菜单,还是展示堆叠菜单项呢?
当我们需要使用并(或者和)来描述模块职责的时候,思考一下是不是在描述两种(甚至更多的)职责。有可能不是。如果是的话,我们就需要为每个职责分别定义模块。这是模块封装的一个非常重要的原则,我们把它叫作单一职责原则( Single Responsibility Principle)。尽可能把多种功能分散到不同的模块中,这样每个模块就可以保持精炼、聚焦,并且容易理解。

3.1. 拆分不同模块的职责

下面我们用两个不同的模块来创建下拉菜单。第一个模块可以叫作下拉( dropdown),其中包含一个控制容器可见性的按钮。换句话说,这个模块负责展示和隐藏容器。我们也可以描述按钮的外观和代表行为的小三角。阐述模块的细节虽然需要用到(或者),但是这些细节都是从属于首要职责的, 因此这么做没问题。
第二个模块叫作菜单,是放置链接的列表。把菜单模块的一个实例放入下拉模块的容器内,就可以构成完整的界面了。
把代码清单11中的代码加入到页面中。这段代码主体是一个下拉模块,下拉模块内部包含了菜单模块。代码中还有一小段 JavaScript,当触发器被点击时用来实现开关的功能。

  1. <div class="dropdown">
  2. <!--下拉的触发按钮-->
  3. <button class="dropdown__toggle">Main Menu</button>
  4. <!--用做菜单容器的抽屉子元素-->
  5. <div class="dropdown__drawer">
  6. <!--放在抽屉内部的菜单模块-->
  7. <ul class="menu">
  8. <li><a href="/">Home</a></li>
  9. <li><a href="/coffees">Coffees</a></li>
  10. <li><a href="/brewers">Brewers</a></li>
  11. <li><a href="/specials">Specials</a></li>
  12. <li><a href="/about">About us</a></li>
  13. </ul>
  14. </div>
  15. </div>
  16. <script type="text/javascript">
  17. (function() {
  18. var toggle = document.querySelector('.dropdown__toggle');
  19. toggle.addEventListener('click', function(event) {
  20. event.preventDefault();
  21. var dropdown = event.target.parentNode;
  22. dropdown.classList.toggle('is-open')
  23. })
  24. })();
  25. </script>

这里使用了双下划线标记,表示触发器和抽屉是下拉模块的子元素。点击触发器可以显示或者隐藏抽屉元素。 JavaScript 代码为下拉模块的主元素添加或者移除 is-open 类,以此来实现这个功能。
下拉模块的样式如代码清单12所示,将其添加到样式表中。这样就实现了下拉的功能,不过里面的菜单目前还没有样式。

  1. /*为绝对定位的抽屉元素建立一个包含块*/
  2. .dropdown {
  3. display: inline-block;
  4. position: relative;
  5. }
  6. .dropdown__toggle {
  7. padding: 0.5em 2em 0.5em 1.5em;
  8. border: 1px solid #ccc;
  9. font-size: 1rem;
  10. background-color: #eee;
  11. }
  12. .dropdown__toggle::after {
  13. content: "";
  14. position: absolute;
  15. right: 1em;
  16. top: 1em;
  17. border: 0.3em solid;
  18. border-color: black transparent transparent;
  19. }
  20. .dropdown__drawer {
  21. display: none;
  22. position: absolute;
  23. left: 0;
  24. top: 2.1em;
  25. min-width: 100%;
  26. background-color: #eee;
  27. }
  28. .dropdown.is-open .dropdown__toggle::after {
  29. top: 0.7em;
  30. border-color: transparent transparent black;
  31. }
  32. .dropdown.is-open .dropdown__drawer {
  33. display: block;
  34. }

在代码清单12里,主元素使用了相对定位,这样就创建了一个包含块,抽屉元素在包含块内使用绝对定位。代码也为触发按钮提供了一些样式,包括::after 伪元素里的三角形。在添加了 is-open 类之后,它会显示抽屉元素并翻转三角形。
接下来,当我们需要回过头修改某个模块时,就会发现模块越小越好,这有助于迅速理解。

1. 在模块里使用定位

这是我们第一个使用定位的模块,其中创建了模块自己的包含块(主元素的 position: relative)。绝对定位的元素(抽屉元素和::after 伪元素)就是基于同一个模块内的位置来定位的。
应该尽量让需要定位的元素关联到同一个模块内的其他元素。只有这样,我们把模块放在另一个有定位的容器里的时候,才不会弄乱样式。

2. 状态类

is-open 类在下拉模块中有特定的用途。我们在模块里使用 JavaScript 动态地添加或移除它。它也是状态类( state class)的一个示例,因为它代表着模块在当前状态下的表现。
按照惯例,状态类一般以 is-或者 has-开头。这样状态类的目的就会比较明显,它们表示模块当前状态下的一些特征或者即将发生的变化。再举一些状态类的示例,比如 is-expanded、is-loading 或者 has-error 等。这些状态类具体会表现成什么样子取决于使用它们的模块。

重点: 状态类的代码要和模块的其他代码放在一起。使用 JavaScript 动态更改模块表现的时候,要使用状态类去触发改变。

3. 菜单模块

下拉模块已经搞定了,下面开始实现菜单模块。我们不需要关心下拉动作的开和关,这已经在下拉模块里实现了。菜单模块只需要实现链接列表的观感。
样式如代码清单13所示,将其添加到样式表中。

  1. .menu {
  2. margin: 0;
  3. padding-left: 0;
  4. list-style-type: none;
  5. border: 1px solid #999;
  6. }
  7. .menu > li + li {
  8. border-top: 1px solid #999;
  9. }
  10. .menu > li > a {
  11. display: block;
  12. padding: 0.5em 1.5em;
  13. background-color: #eee;
  14. color: #369;
  15. text-decoration: none;
  16. }
  17. .menu > li > a:hover {
  18. background-color: #fff;
  19. }

每个

  • 都是模块的子元素,所以我认为没必要为每个元素添加双下划线类,直接使用后代选择器.menu > li 已经足够明确了。
    菜单模块是完全独立的,并不依赖于下拉模块。这使得代码更简单,因为我们不需要理解在这个模块之前先搞懂另一个,也有助于更加灵活地复用模块。
    我们可以根据不同需要创建其他样式的菜单(变体或者完全不同的模块都可以),用在下拉模块的内部。也可以把菜单模块用在下拉模块以外的任意地方。我们无法预知后面的页面需要什么,但有了可复用的模块,可以一定程度上确保提供前后一致的观感。

    3.2. 模块命名

    为模块命名是个很伤脑筋的事情。开发模块的时候我们可以用临时的名称,但是最终完成之前,一定要注意命名。这可能算是模块化 CSS 开发里最难的部分了。
    回想一下前面的媒体模块,如下图所示,我们用它来展示一张跑步者的图片和跑步小提示。
    image.png
    假设我们还没有为该模块命名,现在有个页面需要用到它,我们可能会叫它跑步提示模块。这个名称很贴切,看上去也比较合适,但是我们可能会用这个模块里的样式去做其他事情。如果使用同样的 UI 元素做别的事情,该怎么办呢?比如延续跑步主题网站的主题,我们可能会使用一连串的模块列出即将举办的赛事信息,这时候还以跑步提示来命名模块就不合适了。
    模块的命名应该有意义,无论使用场景是什么。同时也要避免使用简单地描述视觉效果的名称。把这个模块叫作“带图片的灰盒子”看上去比较通用一些,但是如果之后要改成浅蓝色背景呢?或者重新设计网站呢?这样的名称就不能用了,你还得重新命名,再替换掉 HTML 里所有用到它的地方。
    我们应该换一种思路,思考模块代表什么含义。这一般并不容易。“媒体模块”这个名称就很恰当,它代表了一种图文混排的版式。它给人以强烈的印象,并没有将模块局限于任何特定用法或者视觉实现。
    模块要适用于各种不同场景,而其名称应该简单易记。当网站有很多页面的时候,我们可能会多次用到某个模块。到时候你和团队里其他成员沟通,可能会进行这种对话:“这里用个‘媒体’ ”“这 些‘板块’ 太挤了”。
    目前,我们已经实现了消息模块、媒体模块、下拉模块和菜单模块。一些比较好的模块名称包括面板( panel)、警告( alert)、可折叠的部分( collapsible-section)、表单控制项( form-control)等。如果你从一开始就对网站的整体设计有全面的了解,会有助于命名。例如,你可能觉得有两个 UI 元素都可以叫作板块( tile),然而它们毫不相关,这时候就应该更明确地命名它们(比如媒体板块和标题板块)。
    有些人强制使用两个词来命名每个模块,这样就可以避免模块指代不明确,因为你也不知道什么时候会需要另一个新的板块模块。如果现有的板块模块命名比较明确,新的板块模块出现的时候,再取名就会比较容易,不至于跟前一个混淆。
    为模块的变体类命名的时候,应该遵守同样的原则。例如,如果已经有按钮模块了,就不应该使用 button—red 和 button—blue 命名红色和蓝色变体子类。网站设计在将来有可能会改变,你不知道这些按钮的颜色会不会也跟着变化。应该使用一些更有意义的名称,比如button—danger 和 button—success。
    使用大或小这样具有相对意义的词语来命名修饰符不是最佳方式,但也可以接受。没人说过网站重构的时候不能更改 button—large 的尺寸,只要它还是比标准按钮大一些就可以。一定要牢记,不要使用像 button—20px 这样特别精确的修饰符。

    4. 工具类

    有时候,我们需要用一个类来对元素做一件简单明确的事,比如让文字居中、让元素左浮动,或者清除浮动。这样的类被称为工具类( utility class)。
    从某种意义上讲,工具类有点像小号的模块。工具类应该专注于某种功能,一般只声明一次。我通常把这些工具类放在样式表的底部,模块代码的下面。
    代码清单14展示了四个工具类,它们分别实现了特定的功能:文字居中、左浮动、清除浮动(包裹浮动)、隐藏元素。

    1. .text-center {
    2. text-align: center !important;
    3. }
    4. .float-left {
    5. float: left;
    6. }
    7. /*清除浮动*/
    8. .clearfix::before, .clearfix::after {
    9. content: "";
    10. display: table;
    11. }
    12. .clearfix::after {
    13. clear: both;
    14. }
    15. .hidden {
    16. display: none !important;
    17. }

    这里用到了两次!important。工具类是唯一应该使用 important 注释的地方。事实上,工具类应该优先使用它。这样的话,不管在哪里用到工具类,都可以生效。我敢肯定,任何时候为元素添加 text-center 类,都是想让文本居中,不想让其他样式覆盖它。用了 important 注释就可以确保这一点。
    可以把这些类添加到页面元素里看看实际效果。

    可以使其中的文本居中。将 float-right 添加到模块化CSS - 图6标签可以使其浮动,把 clearfix 添加到模块化CSS - 图7的容器元素上可以使其包裹浮动。工具类的作用立竿见影。在页面上做点小事儿的时候不需要创建一个完整的模块,这种情况下可以用一个工具类来实现。但是不要滥用工具类。对于大部分网站,最多十几个工具类就够用了。

    5. 总结

    • 把 CSS 拆解成可复用的模块。
    • 不要书写可能影响其他模块或者改变其他模块外观的样式。
    • 使用变体类,提供同一模块的不同版本。
    • 把较大的结构拆解成较小的模块,然后把多个模块组合在一起构建页面。
    • 在样式表中,把所有用于同一个模块的样式放在一起。
    • 使用一种命名约定,比如双连字符和双下划线,以便一眼就可以看清楚模块的结构。