BEM 介绍
BEM(Block, Element, Modifier) 是一个基于组件的 web 开发方法。它背后的思想是将 UI 拆分成独立的块(blocks)。这可以更容易和快速开发复杂的 UI ,并且无需复制粘贴就可以重用已有的代码。
Block
一个可以重用的功能上独立的页面组件。在 HTML 中,块用class
属性来体现。
特点:
- 块的名称描述了它的用途(“它是什么?”——
menu
或者是button
),而不是它的状态(“它是什么样的?”——red
或者big
)。
例子
<!-- Correct. The `error` block is semantically meaningful -->
<div class="error"></div>
<!-- Incorrect. It describes the appearance -->
<div class="red-text"></div>
- 块不应该影响它的环境,意味着你不应该设置外部几何形状(margin)或者定位。
- 你也不应该用 CSS 标签或者 ID 选择器。
这能保证块的独立性,能够让它们在不同的地方重用。
使用块的规则
嵌套
- 块可以嵌套使用。
- 可以嵌套任意层。
例子
<!-- `header` block -->
<header class="header">
<!-- Nested `logo` block -->
<div class="logo"></div>
<!-- Nested `search-form` block -->
<form class="search-form"></form>
</header>
Element
块里面复合的部分,不能与块分开使用。
特点:
- 元素的名称描述了它的用途(“它是什么?”——
item
或者是text
),而不是它的状态(“它是什么样的?”——red
或者big
)。 - 一个元素完整的名称的结构应该是
block-name__element-name
。元素名称和块名称使用双下划线(__)分隔的。
例子
<!-- `search-form` block -->
<form class="search-form">
<!-- `input` element in the `search-form` block -->
<input class="search-form__input">
<!-- `button` element in the `search-form` block -->
<button class="search-form__button">Search</button>
</form>
使用元素的规则
嵌套
- 元素可以嵌套使用。
- 可以任意数目的嵌套层级。
- 元素总是块的一部分,而不属于其他元素。这意味着元素名称不能定义成几层,比如
block__elem1__elem2
。
例子
<!--
Correct. The structure of the full element name follows the pattern:
`block-name__element-name`
-->
<form class="search-form">
<div class="search-form__content">
<input class="search-form__input">
<button class="search-form__button">Search</button>
</div>
</form>
<!--
Incorrect. The structure of the full element name doesn't follow the pattern:
`block-name__element-name`
-->
<form class="search-form">
<div class="search-form__content">
<!-- Recommended: `search-form__input` or `search-form__content-input` -->
<input class="search-form__content__input">
<!-- Recommended: `search-form__button` or `search-form__content-button` -->
<button class="search-form__content__button">Search</button>
</div>
</form>
块的名称定义了命名空间,保证了元素是依赖于块的(block__elem
)。
一个块在 DOM 树中可以有一个嵌套的元素的结构:
例子
<div class="block">
<div class="block__elem1">
<div class="block__elem2">
<div class="block__elem3"></div>
</div>
</div>
</div>
然而,在 BEM 中,这个块的结构是被当做一串元素的扁平列表。
例子
.block {}
.block__elem1 {}
.block__elem2 {}
.block__elem3 {}
这种结构使得如果你想要改变块的 DOM 结构,不需要对每个元素的代码做出修改:
例子
<div class="block">
<div class="block__elem1">
<div class="block__elem2"></div>
</div>
<div class="block__elem3"></div>
</div>
块的结构改变了,可是元素的规则以及它们的名称没有改变。
成员身份
一个元素总是一个块的一部分,你也不应该在块以为单独使用它。
例子
<!-- Correct. Elements are located inside the `search-form` block -->
<!-- `search-form` block -->
<form class="search-form">
<!-- `input` element in the `search-form` block -->
<input class="search-form__input">
<!-- `button` element in the `search-form` block -->
<button class="search-form__button">Search</button>
</form>
<!--
Incorrect. Elements are located outside of the context of
the `search-form` block
-->
<!-- `search-form` block -->
<form class="search-form">
</form>
<!-- `input` element in the `search-form` block -->
<input class="search-form__input">
<!-- `button` element in the `search-form` block-->
<button class="search-form__button">Search</button>
可选性
一个元素对于块组件来说是可选的。不是所有块都有元素。
例子
<!-- `search-form` block -->
<div class="search-form">
<!-- `input` block -->
<input class="input">
<!-- `button` block -->
<button class="button">Search</button>
</div>
我应该建立一个块还是元素?
- 如果一段代码可能会被重用,而它也不依赖于正在被实现的其他页面组件,你应该建立一个块。
- 如果这段代码不能在父实体(块)外独立使用,你应该建立一个元素。
此外,有时候元素还必须要被拆分为更小的部分——子元素,这通常是为了简化开发。在 BEM 中,你不能创建元素的元素。这种情况下,你不应该建立一个元素,而应该是一个服务块。
Modifier
一个定义外表、状态、块或者元素的行为的实体。
特点:
- 修饰符的名称应该描述它的外表(“什么大小?”或者“哪个主题?”和其他——
size_s
或者theme_islands
),它的状态(“它跟其他的区别是什么?”——disabled
,focused
等等)和它的行为(“它是怎么表现的?”或者“它是如何相应用户的?”——比如directions_left-top
)。 - 修饰符的名称跟块或者元素的名称使用单下划线(_)分隔的。
修饰符的类型
布尔值
- 只有当修饰符的出现与否是很重要,并且它的值无关紧要时使用。例如:
disabled
。如果一个布尔值修饰符在的话,它的值应该是true
。 - 修饰符完整的名称结构应该遵循以下的样式:
block-name_modifier-name
block-name__element-name_modifier-name
例子
<!-- The `search-form` block has the `focused` Boolean modifier -->
<form class="search-form search-form_focused">
<input class="search-form__input">
<!-- The `button` element has the `disabled` Boolean modifier -->
<button class="search-form__button search-form__button_disabled">Search</button>
</form>
键值
- 当修饰符的值很重要时用。例如“用
islands
设计主题的菜单”:menu_theme_islands
。 - 修饰符完整的名称结构应该遵循以下的样式:
block-name_modifier-name_modifer-value
block-name__element-name_modifer-name_modifier-value
例子
<!-- The `search-form` block has the `theme` modifier with the value `islands` -->
<form class="search-form search-form_theme_islands">
<input class="search-form__input">
<!-- The `button` element has the `size` modifier with the value `m` -->
<button class="search-form__button search-form__button_size_m">Search</button>
</form>
<!-- You can't use two identical modifiers with different values simultaneously -->
<form class="search-form
search-form_theme_islands
search-form_theme_lite">
<input class="search-form__input">
<button class="search-form__button
search-form__button_size_s
search-form__button_size_m">
Search
</button>
</form>
使用修饰符的规则
修饰符不能单独使用
从 BEM 的角度,修饰符不能脱离块或者元素使用。一个修饰符应该改变外观,行为,或者实体的状态,而不是取代它。
例子
<!--
Correct. The `search-form` block has the `theme` modifier with
the value `islands`
-->
<form class="search-form search-form_theme_islands">
<input class="search-form__input">
<button class="search-form__button">Search</button>
</form>
<!-- Incorrect. The modified class `search-form` is missing -->
<form class="search-form_theme_islands">
<input class="search-form__input">
<button class="search-form__button">Search</button>
</form>
混合
在单独的 DOM 节点上使用不同的 BEM 实体的一个技术。
混合可以让你:
- 无需复制代码就可以结合多个实体的行为和样式。
- 基于已有的代码语义化的创建新的 UI 组件。
例子
<!-- `header` block -->
<div class="header">
<!--
The `search-form` block is mixed with the `search-form` element
from the `header` block
-->
<div class="search-form header__search-form"></div>
</div>
在这个例子中,我们混合了search-form
块和header
块里的search-form
元素的行为和样式。这种方法可以让我们对header__search-form
元素设置外部的几何形状和定位,同时让search-form
块本身保持通用。结果是,我们可以在不同的环境中使用这个块,因为它没有指定任何内编辑。这就是为什么我们可以单独的调用它。
文件结构
BEM 采用的组件方法同样应用给文件结构中的项目。块、元素和修饰符的实现被分到独立的文件中,意味着我们可以单独的连接它们。
特点:
- 一个独立的块对应一个单独的目录。
- 块和目录的名称一样。例如,
header
块在header/
目录中,menu
块在menu/
目录中。 - 块的实现被分到独立的稳重中。例如
heaer.css
和header.js
。 - 块目录是放有它的元素和修饰符的子目录的根目录。
- 元素目录的名称以双下划线(__)开头。例如
header/__logo/
和menu/__item/
。 - 修饰符目录的名称以单下划线(_)开头。例如,
header/_fixed/
和menu/_theme_islands/
。 - 元素和修饰符的实现被分到独立的文件。例如
header__input.js
和header_theme_islands.css
。
例子
search-form/ # Directory of the search-form
__input/ # Subdirectory of the search-form__input
search-form__input.css # CSS implementation of the
# search-form__input element
search-form__input.js # JavaScript implementation of the
# search-form__input element
__button/ # Subdirectory of the search-form__button
# element
search-form__button.css
search-form__button.js
_theme/ # Subdirectory of the search-form_theme
# modifier
search-form_theme_islands.css # CSS implementation of the search-form block
# that has the theme modifier with the value
# islands
search-form_theme_lite.css # CSS implementation of the search-form block
# that has the theme modifier with the value
# lite
search-form.css # CSS implementation of the search-form block
search-form.js # JavaScript implementation of the
# search-form block
这样的文件结构使得维护和重用代码更容易。
分支的文件结构假定在生产环境中代码会被组装成项目文件。
你不一定要遵循推荐的文件结构。你可以用其他遵守 BEM 原则的项目结构来组织你的文件,比如: