BEM方法介绍
已发表: 2022-03-11什么是 BEM 方法论?
当您构建较小的网站时,如何组织样式通常不是大问题。 您创建常用文件,编写所有需要的 CSS,仅此而已。 然而,当涉及到更大、更复杂的项目时,如何组织代码变得至关重要。 如果您在一个由多个前端和后端开发人员组成的团队中工作,那么代码的结构就更加重要。
今天,有很多方法旨在减少 CSS 代码并使您的 CSS 代码更易于维护。 在本文中,我将解释并提供其中一个示例:BEM。 BEM 代表B lock Element M odifier 。 它背后的主要思想是加快开发过程,并通过将 CSS 类安排到独立的模块中来简化开发人员的团队合作。 如果你曾经见过像header__form--search
这样的类名,那就是 BEM。 是的,类可以命名很长,但它们都是可读和可理解的。
请注意,最佳实践是仅将 BEM 与类一起使用,而不是 ID,因为类允许您在必要时重复名称并创建更一致的编码结构。 此外,如果你想将你的网站分成有组织的模块,它应该由相同的结构组成:块、元素和修饰符。 其中每个块可以有多个元素,并且块和元素都可以有多个修饰符。 不过,让我们先从基本的 BEM 结构开始,并通过示例对其进行解释。
堵塞
块代表您网站中的一个对象。 将其视为代码的更大结构块。 当今每个网站上最常见的块是页眉、内容、侧边栏、页脚和搜索。 BEM 中的块始终是链接 CSS 类的起点。 看几个块示例:
- 一个内容
- 一份菜单
- 搜索表格
.content {/* Styles */} .menu {/* Styles */} .search {/* Styles */}
元素
元素是块中执行特定功能的组件。 它应该只在其块的上下文中有意义:
- 内容文章
- 一个菜单项
- 搜索输入字段
.content__article {/* Styles */} .menu__item {/* Styles */} .search__input {/* Styles */}
修饰符
修饰符是我们表示块的变化的方式。 如果您曾经使用过 Bootstrap,那么最好的例子就是按钮大小。 按钮大小只是按钮本身的大小变化,这使其成为修饰符:
- 内容精选文章
- 菜单链接
- 带或不带图标的搜索字段
.content__article--featured {/* Styles */} .menu__item--link {/* Styles */} .search__input--icon {/* Styles */}
命名约定
BEM 方法的主要目的是使 CSS 选择器的名称尽可能信息丰富和透明。 原始 BEM 样式是这样定义的:
块名称通常是一个单词,如.header
,但如果你有更长的块定义,那么它会用一个连字符-
分隔:
.lang-switcher {/* Styles */}
元素名称以双下划线__
开头:
.lang-switcher__flag {/* Styles */}
修饰符名称以单下划线_
开头:
.lang-switcher__flag_basic {/* Styles */}
BEM 方法中只有一个非常关键的规则——修饰符不能在其所有者的上下文之外使用。
例子:
.btn_big {/* Styles */}
仅当还定义了标头时才能使用btn_big
。
不好的例子:
<div class=”btn_big”>...</div>
好的例子:
<div class=”btn btn_big”>...</div>
除了这些原始 BEM 样式之外,还有其他命名方案,例如 Harry Roberts 和 CamelCase 样式。
Harry Roberts 风格示例:
.block-name__element-name--modifier-name {/* Styles */}
CamelCase 样式示例:
.BlockName__ElementName_ModifierName {/* Styles */}
其他的也很少,但这两个是最常见的。 就个人而言,我是 Harris Roberts 提出的命名约定的粉丝,它有以下规则:
- 名字是小写的
- BEM 实体名称中的单词用连字符分隔
-
- 元素名称与块名称之间用双下划线
__
分隔 - 布尔修饰符由双连字符分隔
--
- 不使用键值类型修饰符
这种命名约定比其他命名约定更好的原因是您可以很容易地将修饰符元素与其他元素区分开来。 在最初的命名约定中,修饰符的定义如下:
.block__element_modifier {/* Styles */}
但正如你所见,单下划线和双下划线并没有太大区别。 另一方面,双连字符提供了清晰的分隔,您可以立即看到修饰符:
.block__element--modifier {/* Styles */}
不同格式的 BEM 示例
请注意,除了 CSS,BEM 在组织 JSON、XML、树文件或任何支持嵌套的格式方面也非常有用。 将 BEM 方法视为构建 UI 的好方法。
让我们考虑以下以 BEM 格式构造的 HTML:
<header class=”header”> <img class=”header__logo”> <form class=”header__search-from”> <input class=”header__search-from__input” type=”input”> <button class=”header__search-from__button” type=”button”> </form> <div class=”header__lang-switcher”></div> </header>
使用 JSON 和 XML 格式也可以实现相同的目的。
XML:
<block:header> <block:logo/> <block:search-from> <block:input/> <block:button/> </block> <block:lang-switcher/> </block>
JSON:
{ block: 'header', content: [ { block: 'logo' }, { block: 'search-form', content: [ { block: 'input' }, { block: 'button' } ] }, { block: 'lang-switcher' } ] }
BEM 项目的文件系统组织
在 BEM 中,以正确的方式组织文件至关重要。 BEM 不仅为您提供了一个很好的 CSS 类组织并使它们完全可以理解,而且还为您提供了一个非常可维护的文件结构。 让我们举一个示例项目,使用 BEM 文件组织技术和 SASS 文件:
blocks/ input/ __box/ --big/ input__box--big.scss input__box.scss button/ --big/ button--big.scss
正如您在上面看到的,仅通过查看主文件夹中的子文件夹结构,一切都清晰有序。 这样,谁在你之后工作或你是否在某人之后工作都没有区别,因为遵循相同的模式非常容易。
将 BEM 项目划分为平台
除了使用 BEM 方法技术来组织文件之外,您还可以处理更具体的事情。 例如,如果您正在构建一个将完全响应的 Web 项目,并且客户端指定移动设备上的某些块与桌面设备上的完全不同,那么最好将您的 BEM 文件夹结构划分为平台。 在各种平台上组织按钮的示例:
common.blocks/ button/ button.scss desktop.blocks/ button/ buttons.scss mobile.blocks/ button/ button.scss
请注意,如果您想使用 BEM 方法组织整个项目,这只是一个示例。 具有 BEM 结构的文件树不是正确使用 BEM 的强制要求,您可以在项目的某些部分使用 BEM。 到目前为止,我还没有使用这种严格的 BEM 文件结构组织,其中每个元素和修饰符都创建了它的文件。 相反,我只是为具有其元素和修饰符声明的块创建一个文件结构。
BEM 实践
由于您现在熟悉命名约定,我将在实践中演示 BEM 方法。 假设我们有这个 HTML 代码在运行:
<a class=”btn btn--big btn--primary-color” href=”#” title=”Title”> <span class=”btn__price”>$3.99</span> <span class=”btn__text”>Product</span> </a>
应用以下 CSS 标记:
.btn__price {/* Styles */} .btn__text {/* Styles */} .btn--big {/* Styles */} .btn--primary-color {/* Styles */}
现在,不要被误导。 到目前为止,在我们的示例中,我们几乎总是有一个块、元素和修饰符,但并不总是如此。
例如,假设我们有一个名为person的块。 一个人有腿和手,也可以是女性或男性。 如果我们想定义一个有右手的男性,它看起来像这样:
.person--male__hand--right {/* Styles */}
现在您可以看到 BEM 的真正含义。 我们定义了一个修饰符是性别的人。 因为不管一个人是男是女,它都有一只手,而手是一个元素。 同样,每个人都可以拥有右手或左手,这又是一个修饰符。
在另一种情况下,如果我们想用一只手定义一般人,我们将这样做:
.person__hand {/* Styles */}
正如您所注意到的,一旦您熟悉了 BEM,就很容易用它来构建您的 CSS 和 HTML 结构。
将 BEM 与 CSS 预处理器一起使用
就个人而言,我无法想象不使用 CSS 预处理器之一来启动任何新项目。 众所周知,预处理器是一件很棒的事情,它们为我们提供了很多好处,最重要的是它们与 BEM 方法完美匹配。
在下面的示例中,您可以看到 BEM 与 SASS 结合的最典型示例:
.person { &__hand {/* Styles */} &__leg {/* Styles */} &--male { /* Styles */ &__hand { /* Styles */ &--left {/* Styles */} &--right {/* Styles */} } &__leg { /* Styles */ &--left {/* Styles */} &--right {/* Styles */} } } &--female { /* Styles */ &__hand { /* Styles */ &--left {/* Styles */} &--right {/* Styles */} } &__leg { /* Styles */ &--left {/* Styles */} &--right {/* Styles */} } } }
SASS 代码将编译成以下 CSS:

.person__hand {/* Styles */} .person__leg {/* Styles */} .person--male {/* Styles */} .person--male__hand {/* Styles */} .person--male__hand--left {/* Styles */} .person--male__hand--right {/* Styles */} .person--male__leg {/* Styles */} .person--male__leg--left {/* Styles */} .person--male__leg--right {/* Styles */} .person--female {/* Styles */} .person--female__hand {/* Styles */} .person--female__hand--left {/* Styles */} .person--female__hand--right {/* Styles */} .person--female__leg {/* Styles */} .person--female__leg--left {/* Styles */} .person--female__leg--right {/* Styles */}
如果你想更进一步,你可以使用方便的 BEM 的 SASS mixins:
/// Block Element /// @param {String} $element - Element's name @mixin element($element) { &__#{$element} { @content; } } /// Block Modifier /// @param {String} $modifier - Modifier's name @mixin modifier($modifier) { &--#{$modifier} { @content; } }
你可以像这样使用它:
.person { @include element('hand') {/* Person hand */} @include element('leg') {/* Person leg */} @include modifier('male') { /* Person male */ @include element('hand') { /* Person male hand */ @include modifier('left') { /* Person male left hand */ } @include modifier('right') { /* Person male right hand */ } } } }
这将产生以下 CSS 输出:
.person__hand { /* Person hand */ } .person__leg { /* Person leg */ } .person--male { /* Person male */ } .person--male__hand { /* Person male hand */ } .person--male__hand--left { /* Person male left hand */ } .person--male__hand--right { /* Person male right hand */ }
我知道你很可能不会有这么长时间的用例,但这是一个很好的例子,说明了如何使用 BEM 以及为什么它在小型和大型项目中如此强大。
开始您的 BEM 项目
正如官方 BEM 文档中所解释的,开始您自己的新 BEM 项目的最简单方法是使用现有的 GIT 存储库。 只需使用 Git 克隆命令:
$ git clone https://github.com/bem/project-stub.git
接下来,转到新创建的目录并安装所有依赖项:
$ npm install
将安装所有必需的依赖项:
使用 ENB 构建项目:
$ node_modules/.bin/enb make
运行服务器模式进行开发:
$ node_modules/.bin/enb server
结果,出现以下消息:
Server started at 0.0.0.0:8080
现在,这意味着服务器已启动并正在运行。 您现在可以在此地址上查看结果:
http://localhost:8080/desktop.bundles/index/index.html
如您所见,已经创建了很多元素,这些元素在bemjson
文件中定义,该文件位于此处:
project-stub/desktop.bundles/index/index.bemjson.js
您可以查看和探索生成所有 HTML 的文件的当前结构,您可以在 localhost index.html
文件中看到这些结构。 我们将更改此文件以获取我们在前一章中解释过的“Person”BEM 项目。 您可以从index.bemjson.js
文件中删除(或注释)整个代码,并将其替换为以下代码:
module.exports = { block: 'page', title: 'Person BEM', favicon : '/favicon.ico', head : [ { elem : 'meta', attrs : { name : 'description', content : '' } }, { elem : 'meta', attrs : { name : 'viewport', content : 'width=device-width, initial-scale=1' } }, { elem : 'css', url : 'index.min.css' } ], scripts: [{ elem : 'js', url : 'index.min.js' }], content: [ { block: 'person', content: [ { elem: 'male', content: [ { elem: 'leg', mods: {side: 'left'}, content: 'Male person leg -- left' }, { elem: 'leg', mods: {side: 'right'}, content: 'Male person leg -- right' }, { elem: 'hand', mods: {side: 'left'}, content: 'Male person hand -- left' }, { elem: 'hand', mods: {side: 'right'}, content: 'Male person hand -- right' } ] }, { elem: 'female', content: [ { elem: 'leg', mods: {side: 'left'}, content: 'Female person leg -- left' }, { elem: 'leg', mods: {side: 'right'}, content: 'Female person leg -- right' }, { elem: 'hand', mods: {side: 'left'}, content: 'Female person hand -- left' }, { elem: 'hand', mods: {side: 'right'}, content: 'Female person hand -- right' } ] }, ] } ] };
现在,将生成以下 HTML:
<div class="person"> <div class="person__male"> <div class="person__leg person__leg_side_left"> Male person leg -- left </div> <div class="person__leg person__leg_side_right"> Male person leg -- right </div> <div class="person__hand person__hand_side_left"> Male person hand -- left </div> <div class="person__hand person__hand_side_right"> Male person hand -- right </div> </div> <div class="person__female"> <div class="person__leg person__leg_side_left"> Female person leg -- left </div> <div class="person__leg person__leg_side_right"> Female person leg -- right </div> <div class="person__hand person__hand_side_left"> Female person hand -- left </div> <div class="person__hand person__hand_side_right"> Female person hand -- right </div> </div> </div>
从上面的代码可以看出,在这个场景中使用了默认的 BEM 编码方案,因为我们只是使用 BEM 提供给我们的默认设置。 您可以探索和使用更多命令和选项,例如创建新页面、块或修改 BEM HTML。 这个我就不深入了,都可以在官方的BEM文档中找到。
优点和顾虑
优点
- BEM 非常适合维护。 有多少次你不得不在一个大型项目中追随某人,而你只是太害怕在没有未知的崩溃的情况下改变任何东西? 使用 BEM 时,您知道元素的确切用途以及它可能出现在哪个块中。
- 类名合乎逻辑且直观,团队中的每个成员都知道该元素在网站上的作用。 BEM 为项目中的每个人提供了一种可以共享的声明性语法,因此他们在同一页面上。
- BEM 消除了嵌套的 CSS 选择器。 每一个 HTML 元素都有自己的 CSS 类,通过它的名字你就知道它的用途是什么。 一个选择器来统治它们。
关注点和常见错误
- 不要太深入嵌套。 主要规则应该是不要使用超过两个级别的父级和子级。
- 小心从哪里开始你的块作用域。 这里的一个常见错误是开发人员使用块时,但他没有意识到在开发的后期同一块将具有主父块,这可能会破坏嵌套规则。
- 避免使用 SASS @extend。 引用哈里·罗伯茨的话:
通过不在 Sass 中将类“捆绑”在一起,您可以在视图中创建更多的组合。 HTML 有更好的书面记录,因为您可以看到每个类都作用于 DOM 的一部分。 您的 CSS 保持更精简,因为您不必在每次想要创建新的 UI 时创建新的占位符类(或组合它们的清单类)。
结论
当我第一次看到BEM编码方案时,我的第一个想法是:
这些课程太长了,无法编写和阅读。
但是在尝试过之后,现在我无法想象不使用它就开始一个新项目。 对我来说,BEM 极大地提高了我的代码可维护性,我可以肯定地说,每个将被“投入”到基于 BEM 的项目中的开发人员都会很快赶上整个代码结构。
尽管如此,社交网络上还是有很多关于 BEM 的讨论。 有人说 BEM 不好,想知道他们为什么要编写这么长的名称类而不是只使用默认的 HTML 嵌套元素。 好吧,没有人说你必须喜欢 BEM,但事实是大多数前端开发人员都接受它并发现它非常有用。