使用 BEM 的几个注意事项

最近在做新的博客主题,又恰巧在整理 HTML 中为元素命名方面的心得,就借机重温了一下 BEM 文档。或许是太久没看官方文档,我发现 BEM 如今已经延伸到了 Web 开发实践的范畴,而不再是一个单纯的命名规范。好坏不谈,总结一下注意事项。


命名规则

过去的 BEM 语法冗长丑陋,期间也经历了几种方式(现在依然共存):

/* 旧:破折号式 */
.block-name {}
.block-name__element-name {}
.block-name__element-name--modifier-name {}

/* 旧:驼峰式 */
.blockName {}
.blockName--modifierName {}
.blockName__elementName--modifierName {}

而现在的命名规则主要是对修饰符的连接符做了调整——破折号改为单下划线链接:

.block-name {}
.block-name__element-name_modifier-name {}

如何使用块与元素

  • 如果一个区域可以复用且不依赖其它组件,则可作为一个块(Block)。
  • 如果一个区域不能拿到外部单独使用,那么就应该作为一个元素(Element)。
  • 元素不能单独使用,只能作为块的一部分使用。元素不能包含元素,因为这样做会妨碍块内部元素位置的层级调整。
    <!-- 错误范例 -->
    <form class="search-form">
        <div class="search-form__content">
            <!-- 应该使用: search-form__input 或 search-form__content-input -->
            <input class="search-form__content__input">
            <!-- 应该使用:search-form__button 或 search-form__content-button -->
            <button class="search-form__content__button">搜索</button>
        </div>
    </form>
  • 如果一个元素可以继续分解,则可使用「元素 + 块」混合的方式另起一个空间,下面会单独介绍。

修饰符的改进

修饰符(Modifier)用来修饰块或元素的外表、状态或行为。修饰符不能单独使用,而且必须绑定在对应的块或元素上,不能混搭。

<button class="button button_active">...</button>

现在除了布尔值修饰符,还可以使用 Key-value 形式。

  • 一般来讲,如果修饰符的值可有可无(disabled、readonly 等,有则是 true,没有则是 false),那么可以省略修饰符的值。
  • 如果修饰符的值可以有多种状态,则使用 Key-value 形式。

另外,因为修饰符的连接符从 -- 变成了 _,所以并不像过去那么丑陋了。

/* 使用布尔值表示状态(禁用、可见、聚焦等) */
.btn_disabled {}
.header__search-form_focused {}
 /* 使用 Key-value 表示选项(尺寸、主题、类型等) */
.btn_size_s {}
.search-form_theme_dark {}
.menu__item_type_text {}

为什么修饰符一定要包含块名或元素名?

  • 每一个块都会建立独立的命名空间,可以有效的减少命名冲突;
  • 显式说明修饰符所对应的块或元素,防止混淆;
  • 代码搜索更加准确方便;
  • 不包含块名或元素名,使用修饰符就必须使用组合选择器(.btn.disabled),这会增加样式权重使其难以覆盖。

使用混合拆分样式

在 BEM 中,位置和布局样式通过父级块来进行设置。这就需要通过混合(Mix)组合块与元素,组合多个实体(块、元素、修饰符都被称作 BEM 实体)的表现与样式,同时不耦合代码。

<!-- header 块 -->
<div class="header">
    <!-- `search-form` 块混合 header 块的 `search-form` 元素 -->
    <div class="search-form header__search-form"></div>
</div>

上面的例子通过混合的方式把位置样式从块中剥离,然后就可以在 .header__search-form {} 中设置表单的位置或浮动等样式,从而保持了 search-form 块的样式独立,对其完整样式代码进行了解耦。这并不陌生——在传统命名方式中,我们经常通过嵌套的方式(.header .search-form)对局部样式进行调整。但这样做会改变选择器的权重。在 BEM 的思想中,保持选择器扁平和低权重是一个准则。

因此,在使用 BEM 时需要格外注意遵循它的工作方式:

  • 不在块里设置位置、布局相关的样式,只设置基本样式;
  • 通过混合的方式,在作为父级块的元素时设置布局样式;
  • 适时拆分元素为独立的块,解耦样式并形成新的命名空间。

避免组合使用标签与类选择器

在 CSS 中使用 tag 一直不是一个好的实践,但在有些时候却难以避免。唯一需要注意的是 a.btn 是无法被 .btn_active 覆盖的。因此组合使用标签与类选择器时要格外注意。当然,最好还是避免使用。

适时使用嵌套选择器

很多人说 BEM 是「禁止嵌套」的,这不完全准确。BEM 只是建议尽量保持嵌套层级越低越好。因为 CSS 的权重问题很多人处理不好,最终恶果就是不停的嵌套和为了增加权重而进行的无意义嵌套和 !important,这无疑增加了代码的耦合。

在你需要通过块状态对内部元素进行调整时,使用嵌套是很好的选择:

.btn_active .btn__text {}
.search-form_foucsed .search-form__input {}
.nav_theme_dark .nav__item {}

块不一定是一个视觉上的区块

在批量设置多个样式时,可以使用混合的方式建立一个纯样式抽象的「块」。此时块不对应页面上具体的一块区域,而是代表一组抽象样式。

<article class="article text"></article>
<footer class="footer">
    <div class="copyright text"></div>
</footer>

上面的例子把 .text 作为一个单独的文本样式块,用来声明特定的一种文字设置。

.text {
    font-family: Arial, sans-serif;
    font-size: 14px;
    color: #000;
}

同理,页面整体布局、栅格系统等,都可以作为单独的块来设置。

开放/关闭原则

任何 HTML 元素都应遵循向修饰符开放并拒绝改变的原则。换句话说,就是一个 HTML 元素应该便于通过修饰符对齐进行样式覆盖,但是不应该频繁对其本身进行修改,因为每次改动都将影响到所有元素。

切换到 BEM 式 CSS

  • 把 DOM 模型扔到一边,学习创造块。
  • 不要使用 ID 选择器和标签选择器。
  • 最小化选择器嵌套。
  • 使用 class 命名约定来避免命名冲突,确保选择器名称具备自解释性。
  • 用块、元素和修饰符的方式工作。
  • 把可能会改变的 CSS 样式属性从块挪到修饰符里去。
  • 使用混合。
  • 把代码拆分成独立的部分从而更容易的使用单独的块。
  • 重复使用块。

总结

使用 BEM 的好处有很多:

  • 自解释性很强,看到即看懂。
  • 强制 「块--元素_修饰符」结构,命名重复可能性大大降低,有效地防止了样式污染问题。
  • 抽象核心样式,通过修饰符的方式减少代码复制。
  • 提供良好的扩展性。

而且现在 BEM 除了针对 CSS 的方案,在 JavaScript、文件解构上也有了充分设计。按照官方的姿态,BEM 已经不应再作为 SMACSS 或 OOCSS 的类比,而是接近 Web Components 的产品,同时提供了完整的针对项目的构造方案。

我个人还是更喜欢把它当做一个单纯的 CSS 命名方案来看待,因为在组件化开发的方面,现在有很多更好的方案可供选择。另外 BEM 也不是万能灵药,它虽然解决了命名冲突、权重混乱等问题,但在模块组织上并不是特别清晰,借鉴一些 SMACSS 中的思想会让你的项目更加清晰有条理。

光说不练假把式,在接下来的新主题中就会用 BEM + SMACSS 来操练一番。

-- EOF --

此条目发表在 前端开发 分类目录,贴了 , , 标签。将固定链接加入收藏夹。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

6 + 4 =