编写模块化CSS:BEM

你是否做过多页面的大型网站或者其中一部分?如果你做过,你可能意识到CSS架构不够强大所带来的恐惧。你可能还会研究如何编写可维护的CSS。

由于我们的行业很棒,我们有很多推荐的解决方案。因为专家们的纷纷加入,于是我们有BEM,OOCSS,SMACSS,Atomic Design等许多选择。

现在,问题不是痛苦“我不知道该怎么办”,而是“有这么多方法,我应该尝试哪个?我是不是应该把所有的都用一遍,是不是只有一种方法才适合我,或者我是不是应该参考它们做一个自己的架构?”

当我在寻找一个出色的CSS架构时我究竟在找什么

当我将不同的方法拼凑在一起以形成我自己的习惯时,我会寻找一下四个特点:

  • 我必须 立即知道编辑一个 class 是否安全,会不会干扰其他CSS。这是最重要的,特别是当我需要在短时间内进行修改时。我不想因改变一处而破坏别的东西。
  • 我必须 立即知道一个 class 放在这个伟大工程的什么地方,以防止大脑过载,这样我就可以快速修改 style,而不必在整个工程里前后引用。
  • class 必须 尽可能少,因为看到一长串的 class 我头很晕。
  • 我必须 立即知道一个组件是否使用了 JavaScript,所以如果我改变了它的CSS,我不会以外的破坏了任何组件。

在我的探索中,我发现 BEM命名空间 符合我寻找的标准

从BEM开始

BEM 是我的方法的基础。如果你以前从未听说过BEM,它代表 blockelementmodifier 。当你第一次接触它的时候,他看起来是那么的难看。

.block {/* styles */}
.block__element {/* styles */}
.block--modifier {/* styles */}

当我第一次看见 BEM 的时候,我就很讨厌他,甚至没有给他一次机会。我不记得是什么驱使我尝试 BEM 的,但我现在深深地知道他有多么的强大。让我来完整的解释一下 BEM 是什么(当然,包括了我自己的理解)。

一个块就是一个组件。这有点抽象,让我们用示例来学习。

假设您在建立一个联系表单。在这种情况下,这个表单可以是一个块。在 BEM 中块被写为像 class 的名字一样,如下所示:

.form {/* styles */}

BEM 使用 .form 而不是 <form> 元素的原因是因为 类允许无限的可重用性,而即使是最基本的元素也可能改变样式。

按钮很好的阐释了可以包含不同样式的块。如果将 <button> 元素的背景颜色设置为红色,则所有的 <buttons> 都将被强制继承红色背景。接下来,你必须通过覆盖你的 <button> 元素来修复代码(并有可能在修复过程中“伤及无辜”)。

.button {
  background-color: red;
}

.something button {
  background-color: blue;
}

如果设置了一个 .button 类的按钮,则可以在任何 <button> 上选择是否使用 .button 类。那么,如果你需要一个不同的背景色,你所做的就是改成一个新的 class ,比如说 .button--secondary ,很舒服吧!

.button {
  background-color: red;
}

.button--secondary {
  background-color: blue;
}

这给我们引入了 BEM 的下一部分内容 ——— 修饰符。

修饰符

修饰符是改变某个块的外观的标志。要使用修饰符,可以将 --modifier 添加到块中。

从上面的按钮示例继续,修改的按钮将被命名为 .button--secondary

在传统的 BEM 中,当你使用修饰符时,你应该 将块和修饰符添加 到 HTML 中,以便在新的 .button--secondary 不重写 .button 样式。

<button class="button">Primary button</button>
<button class="button button--secondary">Secondary button</button>

.button {
  padding: .5em .75em;
  background-color: red;
}
.button--secondary {
  background-color: blue;
}

注意为什么没有在 .button--secondary 中重新声明 padding ,因为他已经在 .button 中声明了。这很棒,因为 BEM 确保你编写简洁的 CSS,而不需要付出大量的工作。

但是,我并不喜欢在 HTML 中再加一个 .button ,因为 .button--modifier 已经告诉我们,他是一个带有 --secondary 标志的 .button .理想情况下,我们的 HTML 应该是这样的:

<button class="button">Primary button</button>
<button class="button--secondary">Secondary button</button>

这更简洁,不是吗?

不幸的是,如果 HTML 里面没有 .button ,我们必须回到非简洁的CSS:

.button {
  padding: .5em .75em;
  background-color: red;
}
.button--secondary {
  padding: .5em .75em;
  background-color: blue;
}

额,这么繁琐的东西好恶心。但是有两种方法可以编写简洁的CSS,而不需要额外的 class

方法1:使用mixin

第一种方式,如果使用 Sass 或其他与处理器,则 使用mixin来封装 需要重用的 所有代码 。在我们的按钮示例中,只要将 padding 写入 mixin。在这里,我在块中调用这个 mixin:

@mixin button {
  padding: 0.5em 0.75em;
}
.button {
  @include button;
  background-color: red;
}
.button--secondary {
  @include button;
  background-color: green;
}

OK,世界安静了!

但是,如果我不使用 Sass 怎么办?

别紧张!即将分享的第二种方法是使用普通的CSS,所以你也可以使用它。

方法2:使用CSS属性选择器

第二种方法 使用CSS属性选择器 执行稍微更复杂的选择。我会告诉你它是什么,解释为什么这样做:

[class*='button']:not([class*='button__']) {
  padding: 0.5em 0.75em;
}

现在,这不是你通常看到的选择器,我来解释一下:

第一部分([class*='button'])告诉解析器查找包含文本 button 的所有 class 。(*= 搜索与确切字符串匹配的任何内容)。当然,这意味着 CSS 的目标是 .button.button--modifier 。不幸的是,这也意味着选择器也是针对 BEM 元素,这就是为什么引入第二部分的原因。

第二部分(:not([class*='button__']))告诉解析器将包含 .button__ 的任何东西排除在外,这样就排除了 BEM 元素。(BEM 元素具有 .block__element 语法)。

在这一点上,你让然可能不喜欢 BEM 丑陋的 --modifier 语法。我知道为什么,但我爱上这个语法的原因是 我很讨厌命名 。有时候,我发现需要使用很多单词来命名一个 BEM 块或元素。举个例子 inner-section

因此如果我是用 -modifier(如某些方法简易的),我将无法一眼看出 -sction 是否是修饰符。所以这是一个馊主意。同样的,我也不能立即知道 .button-secondary 是否也是修饰符!

很具有讽刺意味,但是这个丑陋的语法让我们的代码更简洁,更易于维护。所以强烈推荐你尝试它。

让我们来看看 BEM 的第三个重要部分 —— 元素。

元素

元素是块的子节点。为了表明某个东西是一个元素,你需要在块名后面添加 __element 。所以,你如果看到一个像那样的名字,比如 form__row ,你将立即知道 .form 块中有一个 row 元素。

<form class="form" sction="">
  <div class="form__row">
    <!- ... ->
  </div>
</form>

.form__row {
  /* styles */
}

BEM 元素有两个优点:

  • 你可以让 CSS 的优先级保持相对扁平。
  • 你能立即知道那些东西是一个子元素。

为了解释以上两点,考虑使用两个单独的 class 的替代方法。你可能会用这样的东西:

<form class="form" section="">
  <div class="row">
    <!- ... ->
  </div>
</form>

.form .row {
  /* styles */
}

如果你使用 BEM 元素,则可以使用优先级为 10 而不是 20 的选择起来为 .form__row 提供样式。此外,你可以立即分辨出(无论是在 HTML 还是 CSS 中).form__row.form 的子节点。

顺便说一句,如果你还没有克服 __element 语法的丑陋,等你使用到第三方插件看到像蛇一样的 class 名的时候,你就知道了。

继续,有件事你需要了解。永远不应该链式命名 BEM 元素 。如果你的 class 最终像这样 .form__row__input ,你做的事情是非常错误的。

有两种方式可以绕过长长的 BEM 链式命名。他们是:

  • 只把子元素连接到有意义的块。
  • 创建新的块来保存元素。

连接孙元素到块

虽然 BEM 建议你将 BEM 元素写成 .block__element ,但他们不会规定你的 HTML 应该如何。所以,只要有意义的话,你可以吧你的孙元素连在一起。

接下来是一个例子。在下面的代码中,你将看到 .article__header.article 的子元素。 .article__title.article 的孙元素(或者说是 .article__header 的子元素,如果你将它们同时表示为 .article 的子元素,就没有冲突,因为这个表单同时只有他们存在)。

<article class="article">
  <header class="article__header">
    <h1 class="articel__title"></h1>
  </header>
</article>

虽然这样有效,但是你也会遇到无意义的链接孙元素的情况。举个例子:

<section class="comments">
  <h2 class="comments__title"></h2>
  <article class="comments__comment">
    <h3 class="comments__comment-title"></h3>
  </articel>
  <article class="comments__comment">
    <h3 class="comments__comment-title"></h3>
  </article>
</section>

此时你需要创建新的块来保存孙元素。

创建新的块来保存孙元素

在上述情况下,你可以轻松地将 .comments__comment 拆为 .comments.comment

<section class="comments">
  <h2 class="comments__title"></h2>
  <article class="comment">
    <h3 class="comment-title"></h3>
  </article>
    <article class="comment">
    <h3 class="comment-title"></h3>
  </article>
</section>

如果你这样做,请确保将 .comments.comment 块放在同一个文件夹中,以方便参考。

不幸的是,有时候它不像 .comments__comment 那么简单。例如,假设在块中有一个列表元素。

<div class="block">
  <ul class="block__list">
    <li class="block__item">
      <!- how would you name this class? ->
      <h3 class="???"></h3>
    </li>
  </ul>
</div>

如果你质疑道,我已经连接了 .block__item ,这是一个 .block 的孙元素。将 .block__item 中的元素连接到 .block 没有意义,或可能最终会遇到一些糟糕的局面。

然而,同时由于它们被遗弃使用,所以为 .blcok__list.block__item 创建一个新的块是没有意义的。你会命名什么来保持在上下文中有意义?

在这种情况下,我一般会为 block__item 创建一个名为 .item 的伪块。看下面的 HTML。

<div class="block">
  <h3 class="block__title"></h3>
  <ul class="block__list">
    <li class="block__item">
      <h3 class="item__title"></h3>
    </li>
  </ul>
</div>

伪块,正如名字所示,是伪的。上面的 HTML 中没有 .item 的实际声明。但是,在 .block__item 中有链接到 .item 的元素中。

在我的CSS(Sass)中,我在 .block__item 中嵌套 .item 元素,赋予了它所需的上下文。

.block__item {
  .item__title {
    /* styles */
  }
}

你可能会说,“这是违反 BEM 惯例的!”是的,但请阅读下一篇文章。你就知道为什么这样做。

接下来,还有一件事,在我的用力中添加为 BEM 添加的 —— 容器。

容器

有时(实际上经常),我会遇到这样的情况,我必须在确定其他元素都对起的同时扩散一个区域的背景色,就像这样:
这里写图片描述

浅灰色的背景扩散到了对齐的区域的外面

如果你熟悉构建布局,会使用以下方式构建 HTML:

<section>
  <div class="l-wrap">
    <div class="block">
      <!- ... ->
    </div>
  </div>
</section>

问题是,你应该怎么命名块容器?或者在这种情况下,怎么命名 <section> 元素。我习惯的方法时命名为 block-comtainer 。我只在这种情况下使用 container ,所以我觉得他仍然可以接受。

顺便说一下,看见 .l-wrap 中的 .l- 了没,这就是命名空间。

总结

所以,这就是我简单的使用 BEM 的方法。如果你注意到我上面设置的标准,你会注意到我只考虑了两个方面:

  • class 的数量必须尽可能少。
  • 我必须 立即知道一个 class 放在这个伟大工程中的什么地方 ,以防止大脑过载。

其他两方面尚未考虑:

  • 我必须 立即知道组件是否使用 JavaScript
  • 我必须 立即知道编辑一个 class 会否安全,会不会干扰其他CSS
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值