CSS 就像 HTML 的一面镜子一样,嵌套的 CSS 选择器完美地映射了 HTML 结构。

原作者: Adam Wathan 发表于 2017年8月7日

原文链接:https://adamwathan.me/css-utility-classes-and-separation-of-concerns/(opens new window)

在过去的几年里,我编写 CSS 的方式已经从一种非常“语义”的方法过渡到更像通常所说的“功能性 CSS”。

以这种方式编写 CSS 会引起很多开发人员内心直接的抵触,所以我想解释一下自己是如何走到这一步的,并分享在这一过程中所获得的一些经验和见解。

第一阶段:“语义”CSS

当试图学习如何做好 CSS 时,你会听到的最佳实践之一就是“关注点分离”。
这个想法是 HTML 应该只包含关于内容的信息,而所有的样式决定都应该在 CSS 中进行。
看看这个 HTML:

  1. <p class="text-center">
  2. Hello there!
  3. </p>

看到那个 .text-center 类了吗?将文本居中是一个设计决定,所以这段代码违反了“关注点分离”,因为我们让样式信息混入到了 HTML 中。
相反,推荐的做法是根据元素的内容给它们起类名,并在 CSS 中使用这些类作为钩子来设计 HTML 的样式。

  1. <style>
  2. .greeting {
  3. text-align: center;
  4. }
  5. </style>
  6. <p class="greeting">
  7. Hello there!
  8. </p>

这种方法的典型例子就是 CSS Zen Garden(opens new window),这个网站旨在表明,如果你“分开你的关注点”, 只需更换样式表,你就可以完全重新设计一个网站。

而我的工作流程看起来是这样的:

先写下我需要的一些新 UI 的 HTML(在这种情况下是作者的简历卡):

  1. <div>
  2. <img src="https://cdn-images-1.medium.com/max/1600/0*o3c1g40EXj65Fq9k." alt="">
  3. <div>
  4. <h2>Adam Wathan</h2>
  5. <p>
  6. Adam is a rad dude who likes TDD, Active Record, and garlic bread with cheese. He also hosts a decent podcast and has never had a really great haircut.
  7. </p>
  8. </div>
  9. </div>

根据内容添加一两个描述类:

  1. - <div>
  2. + <div class="author-bio">
  3. <img src="https://cdn-images-1.medium.com/max/1600/0*o3c1g40EXj65Fq9k." alt="">
  4. <div>
  5. <h2>Adam Wathan</h2>
  6. <p>
  7. Adam is a rad dude who likes TDD, Active Record, and garlic bread with cheese. He also hosts a decent podcast and has never had a really great haircut.
  8. </p>
  9. </div>
  10. </div>

在 CSS/Less/Sass 中使用这些类作为”钩子”,来设计新的 HTML 。

  1. .author-bio {
  2. background-color: white;
  3. border: 1px solid hsl(0,0%,85%);
  4. border-radius: 4px;
  5. box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  6. overflow: hidden;
  7. > img {
  8. display: block;
  9. width: 100%;
  10. height: auto;
  11. }
  12. > div {
  13. padding: 1rem;
  14. > h2 {
  15. font-size: 1.25rem;
  16. color: rgba(0,0,0,0.8);
  17. }
  18. > p {
  19. font-size: 1rem;
  20. color: rgba(0,0,0,0.75);
  21. line-height: 1.5;
  22. }
  23. }
  24. }

下面是最终结果的演示:
点击查看【codepen】
这种方法对我来说很有意义,有一段时间,我就是这样写 HTML 和 CSS 的。

不过最后,我开始觉得有些不对劲。

我已经“分离了我的关注点”,但 CSS 和 HTML 之间仍然存在着非常明显的耦合。大多数时候,CSS 就像 HTML 的一面镜子一样,嵌套的 CSS 选择器完美地映射了 HTML 结构。

HTML 并不关心样式决定,但 CSS 却非常关心 HTML 结构。
也许我的关注点并没有那么泾渭分明。

第二阶段:将样式从结构中解耦

在四处寻找这种耦合问题的解决方案之后,我开始发现越来越多的建议,即在你的 HTML 中添加更多的类,这样就可以直接针对它们,保持选择器的低特异性,并使 CSS 不那么依赖于特定 DOM 结构

最著名的方法论就是 Block Element Modifer(opens new window),或者简称 BEM。

采用类似 BEM 的方法,上面的作者简历的 HTML 看起来更像是这样:

  1. <div class="author-bio">
  2. <img class="author-bio__image" src="https://cdn-images-1.medium.com/max/1600/0*o3c1g40EXj65Fq9k." alt="">
  3. <div class="author-bio__content">
  4. <h2 class="author-bio__name">Adam Wathan</h2>
  5. <p class="author-bio__body">
  6. Adam is a rad dude who likes TDD, Active Record, and garlic bread with cheese. He also hosts a decent podcast and has never had a really great haircut.
  7. </p>
  8. </div>
  9. </div>

…而 CSS 会是这样的:

  1. .author-bio {
  2. background-color: white;
  3. border: 1px solid hsl(0,0%,85%);
  4. border-radius: 4px;
  5. box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  6. overflow: hidden;
  7. }
  8. .author-bio__image {
  9. display: block;
  10. width: 100%;
  11. height: auto;
  12. }
  13. .author-bio__content {
  14. padding: 1rem;
  15. }
  16. .author-bio__name {
  17. font-size: 1.25rem;
  18. color: rgba(0,0,0,0.8);
  19. }
  20. .author-bio__body {
  21. font-size: 1rem;
  22. color: rgba(0,0,0,0.75);
  23. line-height: 1.5;
  24. }

点击查看【codepen】
这对我来说是一个巨大的进步,我的 HTML 仍然是“语义的”,而且不包含任何样式决定,现在我的 CSS 感觉与我的 HTML 结构解耦了,而且还能避免不必要的选择器特异性。
但后来我遇到了一个难题。

处理类似的组件

假设需要给网站增加一个新的功能:以卡片的形式显示文章的预览,

假设文章的预览卡顶部有一个完整的出血图像,下面有一个填充的内容部分,一个粗体标题和一些较小的正文,

假设让它尽可能看起来像一个作者的简历:
image.png
如何处理这些问题,同时又能分离我们的关注点呢?

我们不能把 .author-bio 类应用到文章预览中,那样就不符合语义了,所以我们一定要让 .article-preview 成为独立的组件。

下面是 HTML 可以成为的样子:

  1. <div class="article-preview">
  2. <img class="article-preview__image" src="https://i.vimeocdn.com/video/585037904_1280x720.webp" alt="">
  3. <div class="article-preview__content">
  4. <h2 class="article-preview__title">Stubbing Eloquent Relations for Faster Tests</h2>
  5. <p class="article-preview__body">
  6. In this quick blog post and screencast, I share a trick I use to speed up tests that use Eloquent relationships but don't really depend on database functionality.
  7. </p>
  8. </div>
  9. </div>

但是我们应该如何处理 CSS 呢?

方案1:复制样式

一种方法是直接复制 .author-bio 样式并重命名这些类:

  1. .article-preview {
  2. background-color: white;
  3. border: 1px solid hsl(0,0%,85%);
  4. border-radius: 4px;
  5. box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  6. overflow: hidden;
  7. }
  8. .article-preview__image {
  9. display: block;
  10. width: 100%;
  11. height: auto;
  12. }
  13. .article-preview__content {
  14. padding: 1rem;
  15. }
  16. .article-preview__title {
  17. font-size: 1.25rem;
  18. color: rgba(0,0,0,0.8);
  19. }
  20. .article-preview__body {
  21. font-size: 1rem;
  22. color: rgba(0,0,0,0.75);
  23. line-height: 1.5;
  24. }

这可以工作,但当然不是很 DRY。对于这些组件而言,以稍微不同的方式(可能是不同的填充或字体颜色)进行差异化也可能太容易了,但这可能导致外观的不一致。

方案2: @extend 作者简历组件

  1. .article-preview {
  2. @extend .author-bio;
  3. }
  4. .article-preview__image {
  5. @extend .author-bio__image;
  6. }
  7. .article-preview__content {
  8. @extend .author-bio__content;
  9. }
  10. .article-preview__title {
  11. @extend .author-bio__name;
  12. }
  13. .article-preview__body {
  14. @extend .author-bio__body;
  15. }

点击查看【codepen】
一般情况下,根本不建议使用 @extend,但是除此之外,这似乎可以解决我们的问题,对吗?
我们已经删除了 CSS 中的重复内容,我们的 HTML 仍然不受样式决定的影响。

“关注点分离”是个稻草人

当你从“关注点分离”的角度来思考 HTML 和 CSS 之间的关系时,它们是黑白分明的。

你要么有关注点分离(好!),要么没有(坏!)。

这不是思考 HTML 和 CSS 的正确方式。
相反,要考虑依赖的方向。

有两种方法可以编写 HTML 和 CSS。

1. ~“关注点分离”~

依赖于 HTML 的 CSS。
根据你的内容来命名你的类(比如.author-bio),将你的 HTML 视为你的 CSS 的依赖。
HTML 是独立的,它并不关心你如何让它看起来,它只是暴露了 HTML 所控制的钩子,比如 .author-bio。
另一方面,你的 CSS 不是独立的,它需要知道你的 HTML 决定暴露哪些类,并需要针对这些类来为 HTML 设计样式。
在这个模型中,你的 HTML 可以重新样式化,但是你的 CSS 是不可重用的

2. ~“混合关注”~

依赖于 CSS 的 HTML。
以内容无关的方式将类命名为你的 UI 中的重复模式(比如 .media-card ),将 CSS 视为你的 HTML 的依赖。
CSS 是独立的,它并不关心被应用于什么内容,而只是暴露了一组可以应用于 HTML 的构件。
HTML 不是独立的,它使用的是 CSS 提供的类,它需要知道有哪些类存在,以便根据需要将它们组合起来,以实现所需的设计。
在这种模式下,CSS 可以重用,但 HTML 不能重新设计
CSS Zen Garden 采用的是第一种方法,而像 BootstrapBulma这样的 UI 框架采用的是第二种方法。
这本质上都不是“错误”的,它们仅仅是根据在特定的环境中什么对你更重要而做出的决定。
对于你正在做的项目,什么更有价值:可重塑的 HTML,还是可重用的 CSS?

选择可重用性

当我读到 Nicolas Gallagher 的《关于 HTML 语义和前端架构》时,我的思维迎来了转变。
我不会在这里重申他的所有观点,但不用说,我从那篇博文中完全相信,为可重用的 CSS 进行优化将是我所从事项目的正确选择。

第三阶段:与内容无关的 CSS 组件

这时,我的目标是明确避免创建基于我的内容的类,而是试图以一种尽可能可重用的方式来命名所有的东西。
这就导致了像这样的类名:

  • .card
  • .btn, .btn—primary, .btn—secondary
  • .badge
  • .card-list, .card-list-item
  • .img—round
  • .modal-form, .modal-form-section

…等等
当我开始关注创建可重用的类时,也注意到了另外一件事。
一个组件做的事情越多,或者一个组件越具体,就越难重用。
这里有一个很直观的例子。
假设我们正在构建一个表单,有几个表单部分,底部有一个提交按钮。
如果我们把所有的表单内容都看作是 .stacked-form 组件的一部分,我们可以给提交按钮一个类似 .stacked-form__button 的类。

  1. <form class="stacked-form" action="#">
  2. <div class="stacked-form__section">
  3. <!-- ... -->
  4. </div>
  5. <div class="stacked-form__section">
  6. <!-- ... -->
  7. </div>
  8. <div class="stacked-form__section">
  9. <button class="stacked-form__button">Submit</button>
  10. </div>
  11. </form>

但也许在我们的网站上还有另一个按钮,它不是表单的一部分,我们需要用同样的方式来实现。

在那个按钮上使用 .stacked-form__button 类是没有意义的,因为它不是堆叠表单的一部分。

不过这两个按钮都是各自页面上的主要操作,所以如果我们根据组件的共同点来命名按钮,并将其称为 .btn—primary,完全去掉 .stacked-form__ 前缀,会怎么样呢?

  1. <form class="stacked-form" action="#">
  2. <!-- ... -->
  3. <div class="stacked-form__section">
  4. - <button class="stacked-form__button">Submit</button>
  5. + <button class="btn btn--primary">Submit</button>
  6. </div>
  7. </form>

现在假设我们想让这个堆叠的表单看起来像在一个浮动的卡片中。
一种方法是创建一个修饰符并将其应用到这个表单中。

  1. - <form class="stacked-form" action="#">
  2. + <form class="stacked-form stacked-form--card" action="#">
  3. <!-- ... -->
  4. </form>

但是如果我们已经有了一个 .card 类,为什么不使用现有的 card 和 stacked form 来组成这个新的 UI 呢?

  1. + <div class="card">
  2. <form class="stacked-form" action="#">
  3. <!-- ... -->
  4. </form>
  5. + </div>

通过采取这种方法,我们有一个 .card,可以成为任何内容的家,还有一个不经意的 .stacked-form,可以在任何容器里面使用。
我们从组件中得到了更多的重用,而且我们不必编写任何新的 CSS。

updating….

原文

https://tailwindchina.com/translations/css-utility-classes-and-separation-of-concerns.html#%E7%AC%AC%E4%B8%80%E9%98%B6%E6%AE%B5-%E8%AF%AD%E4%B9%89-css