BEM 命名规范
在构建较小的项目时,如何组织代码通常不是一个大问题。但是,当涉及到较大、较复杂的项目时,良好的代码组织就成为至关重要的因素。尤其是在多人协作的团队环境中,代码规范更显得至关紧要。
背景
在编程领域,CSS 类名的命名一直是一个具有挑战性的任务,而随意的命名方式可能导致代码的混乱和缺乏一致性。因此,为了解决这一问题,出现了不同的方法,其中一种广受欢迎的方法被称为 块元素修饰符 (BEM) 命名规范。采用 BEM 命名规范可以显著提高代码的灵活性、复用性和可维护性等。
除此之外,还有很多方法旨在帮助开发人员更有效地管理和维护 CSS 代码。例如:
- OOCSS:这种方法通过将 CSS "对象" 与容器和内容分离来减少冗余。
- SMACSS:SMACSS 提供了包含五个 CSS 规则类别的样式指南,有助于更系统地组织和管理样式。
- SUITCSS:SUITCSS 强调结构化的类名和有意义的连字符,以促进清晰的代码结构。
- Atomic CSS:原子 CSS 将样式分解为原子或不可分割的部分,使得可以更灵活地组合和重用样式。
你可以访问这里获取更多关于这些方法的详细信息。
介绍
BEM (Block,Element,Modifier) 是一种基于组件的 Web 开发方法,它是 HTML 和 CSS 类名的流行命名规范。这个方法的核心思想是将用户界面 (UI) 划分为独立的块,从而使得即使在处理复杂的 UI 时,也能够轻松、迅速地进行界面开发。同时,BEM 还允许在不用重复复制和粘贴代码的情况下,方便地重用现有的代码。这种模块化和可维护性强的方法有助于提高开发效率,确保代码的一致性和可读性。
如果你曾经看到过像 el-button--primary
这样的类名,那就是 BEM 的实际应用。可以发现,虽然类名可以命名得很长,但它们都是可读、可理解的。
结构
Block 块
块是 BEM 命名规范的一个部分。块是独立的、可重用的 UI 组件。因此,我们可以把 UI 划分为多个独立的块,例如:header
头部、menu
菜单、form
表单等。
特征:
- 块的命名应该描述其目的 (即它是什么),而不是描述块的状态或样式 (即它看起来像什么)。这意味着块名称应该反映其功能或用途,而不是具体的视觉属性。例如,使用
menu
或button
来描述块,而不是red
或big
来描述块的外观特征。这种方法有助于使代码更加模块化和可维护,因为它强调了块的用途,而不是具体的样式细节。例如:
<!-- 正确的。`button` 块在语义上描述了它是什么 -->
<div class="button"></div>
<!-- 不正确的。它描述了外观 -->
<div class="red-text"></div>
- 块应该是相对独立的单元,不应该影响其环境。这意味着块应该不设置外部几何图形 (如:margin、position),以确保块可以在不同上下文中进行重用而不会破坏页面布局。这种独立性有助于减少副作用,提高代码的可维护性,使开发人员能够更自信地使用和组合不同的块,而不必担心它们会干扰其他部分的布局或外观。这也有助于确保代码更容易维护和扩展。
- 使用 BEM 时,你应该避免使用 CSS 标签或
ID
选择器。这有助于确保样式与 HTML 结构解耦,提高代码的灵活性和复用性。- 避免使用标签选择器:不要使用像
div
、p
或h1
这样的 HTML 标签作为选择器,因为这会将样式与特定的 HTML 元素耦合在一起。 - 避免使用 ID 选择器:不要使用像
#myElement
这样的 ID 选择器。ID 选择器具有较高的特异性,可能导致样式覆盖问题和难以维护的代码。
- 避免使用标签选择器:不要使用像
使用准则
嵌 套
块可以相互嵌套,且可以有任意数量的嵌套级别。
<!-- `header` 块 -->
<header class="header">
<!-- 嵌套 `logo` 块 -->
<div class="logo"></div>
<!-- 嵌套 `search-form` 块 -->
<form class="search-form"></form>
</header>
Element 元素
元素是 BEM 命名规范的一个部分。元素是块的内部组件,用于构成块的结构。因此,它不能在该块之外独立存在。
特征:
**元素的命名应该描述其用途 (即它是什么),而不是描述元素的状态或样式 (即它的类型或看起来像什么)。这意味着元素名称应该反映其在块内的功能或角色。**例如,使用
item
或text
来描述元素,而不是red
或big
来描述其外观或样式。元素全名的结构是:
block-name__element-name
。元素名称与块名称之间用双下划线 (__
) 分隔。例如:html<!-- `search-form` 块 --> <form class="search-form"> <!-- `input` 元素在 `search-form` 块中 --> <input class="search-form__input" /> <!-- `button` 元素在 `search-form` 块中 --> <button class="search-form__button">Search</button> </form>
使用准则
嵌套
元素可以相互嵌套,且可以有任意数量的嵌套级别。
在 HTML 中,块的 DOM 结构可以具有嵌套的元素:
<div class="block">
<div class="block__elem1">
<div class="block__elem2">
<div class="block__elem3"></div>
</div>
</div>
</div>
但是,在 CSS 中,应该保持使用元素的平面列表来表示:
/* 正确。元素以平面列表来表示 */
.block {
}
.block__elem1 {
}
.block__elem2 {
}
.block__elem3 {
}
/* 不正确。元素以嵌套结构来表示 */
.block {
}
.block__elem1 {
}
.block__elem1 .block__elem2 {
}
.block__elem1 .block__elem2 .block__elem3 {
}
在 SCSS 中:
/* 正确。元素以平面列表来表示 */
.block {
&__elem1 {
}
&__elem2 {
}
&__elem3 {
}
}
/* 不正确。元素以嵌套结构来表示 */
.block {
&__elem1 {
&__elem2 {
&__elem3 {
}
}
}
}
这允许你更改块的 DOM 结构,而无需更改每个单独元素的 CSS 代码:
<div class="block">
<div class="block__elem1">
<div class="block__elem2"></div>
</div>
<div class="block__elem3"></div>
</div>
块的结构发生了变化,但元素的规则及其名称保持不变。这种方法提供了更大的灵活性、复用性和可维护性,允许开发人员轻松地调整界面的结构,而不会干扰元素的样式或功能。
元素始终是块的一部分,而不是另一个元素。这意味着元素名称不能定义层次结构,例如:block__elem1__elem2
。
<!--
正确的。完整元素名称的结构遵循该模式: `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>
<!--
不正确的。完整元素名称的结构不遵循该模式: `block-name__element-name`
-->
<form class="search-form">
<div class="search-form__content">
<!-- 推荐: `search-form__input` or `search-form__content-input` -->
<input class="search-form__content__input" />
<!-- 推荐: `search-form__button` or `search-form__content-button` -->
<button class="search-form__content__button">Search</button>
</div>
</form>
会员身份
元素始终是块的一部分,不应将其与块分开使用。
<!-- 正确的。元素位于 `search-form` 块中 -->
<!-- `search-form` 块 -->
<form class="search-form">
<!-- `input` 元素在 `search-form` 块中 -->
<input class="search-form__input" />
<!-- `button` 元素在 `search-form` 块中 -->
<button class="search-form__button">Search</button>
</form>
<!--
不正确的。元素位于 `search-form` 块的上下文之外
-->
<!-- `search-form` 块 -->
<form class="search-form"></form>
<!-- `input` 元素不在 `search-form` 块中 -->
<input class="search-form__input" />
<!-- `button` 元素不在 `search-form` 块中 -->
<button class="search-form__button">Search</button>
可选性
元素是可选的块组件。并非所有块都有元素。
<!-- `search-form` 块 -->
<div class="search-form">
<!-- `input` 块 -->
<input class="input" />
<!-- `button` 块 -->
<button class="button">Search</button>
</div>
Modifier 修饰符
修饰符是 BEM 命名规范的一个部分。修饰符用于在不修改块或元素的基本类名的情况下,对块或元素的外观、状态或行为进行样式上的调整。这种做法有助于保持代码的一致性,提高可维护性和复用性,减少重复编写样式的工作。
特征:
- 修饰符名称用于描述块或元素的外观 (例如,"什么大小?" 或 "哪个主题?",如:
size_s
、theme_islands
)、状态 ("它与其他主题有何不同?",如:disabled
、focused
) 以及行为 ("它的行为如何?" 或 "它如何响应用户?",如:directions_left-top
)。修饰符的使用有助于更清晰地定义和区分不同块或元素的特征,从而提高了代码的可读性和维护性。 - 修饰符名称与块或元素名称之间用单个下划线 (
_
) 分隔,例如:block-name__element-name_modifier-name
。
除了标准的 BEM 修饰符分隔方式之外,还存在其他替代的分隔方案,例如:Harry Roberts 风格。在这种风格中,它使用双连字符 (
--
) 作为分隔符,例如:block-name__element-name--modifier-name
。尽管在单下划线和双连字符之间没有太大的区别,但双连字符提供了一种清晰的方式来区分修饰符,使其在视觉上与元素双下划线 (__
) 有所不同,使你能够立即识别修饰符。注意,无论你选择使用哪种方式,关键是在整个项目中保持一致性,以确保代码的一致性和可维护性。
修饰符的类型
布尔
当只有修饰符的存在与否非常重要,而其具体值无关紧要时,可以简化修饰符的命名。例如,对于一个表示 "按钮是否被禁用的修饰符",你可以将其简单命名为
disabled
,而不必指定值。在这种情况下,如果修饰符存在于块或元素中,则假定其值为true
,如果不存在,则假定其值为false
。修饰符全名的结构遵循以下模式:
block-name_modifier-name
block-name__element-name_modifier-name
html<!-- `search-form` 块有 `focused` 布尔修饰符 --> <form class="search-form search-form_focused"> <input class="search-form__input" /> <!-- `button` 元素有 `disabled` 布尔修饰符 --> <button class="search-form__button search-form__button_disabled"> Search </button> </form>
键值
当修饰符值非常重要时。例如,对于一个表示 "菜单具有特定的设计主题 (如:
islands
)",你可以命名为:menu_theme_islands
。在这种情况下,修饰符的键为theme
,修饰符的值为islands
。修饰符全名的结构遵循以下模式:
block-name_modifier-name_modifier-value
block-name__element-name_modifier-name_modifier-value
html<!-- `search-form` 块有 `theme` 修饰符,其值为 `islands` --> <form class="search-form search-form_theme_islands"> <input class="search-form__input" /> <!-- `button` 元素有 `size` 修饰符,其值为 `m` --> <button class="search-form__button search-form__button_size_m"> Search </button> </form> <!-- 你不能同时使用两个具有不同值的相同修饰符。 --> <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 的角度来看,修饰符不能与修改后的块或元素隔离使用,这是因为修饰符的主要目的是修改块或元素的外观、行为或状态,而不是替代它们。例如:
<!--
正确的。`search-form` 块有 `theme` 修饰符,其值为 `islands`
-->
<form class="search-form search-form_theme_islands">
<input class="search-form__input" />
<button class="search-form__button">Search</button>
</form>
<!-- 不正确。修改后的类 `search-form` 块丢失了 -->
<form class="search-form_theme_islands">
<input class="search-form__input" />
<button class="search-form__button">Search</button>
</form>
小结
综上所述,BEM 类的命名通常可以归纳为以下几种:
块的命名: 使用块名称来定义块的基本样式和行为。
.block-name {}
块和元素的命名: 使用块名称和元素名称的组合来表示块内元素的样式和行为。
.block-name__element-name {}
块和修饰符的命名: 使用块名称和修饰符名称的组合来表示块的变体或状态。
.block-name--modifier-name {}
块、元素和修饰符的命名: 结合块、元素和修饰符的名称,以描述块内元素的变体或状态。
.block-name__element-name--modifier-name {}
混合
混合是一种在单个 DOM 节点上使用不同 BEM 实体的技术。
混合允许你:
- 组合多个实体的行为和样式,而无需重复代码。
- 基于现有 UI 组件创建语义上新的 UI 组件。
例如:
<!-- `header` 块 -->
<div class="header">
<!--
`search-form` 块与 `header` 块中的 `search-form` 元素混合在一起
-->
<div class="search-form header__search-form"></div>
</div>
在此示例中,我们将块 search-form
块与 header
块中的 search-form
元素混合在一起。这意味着你可以在 header__search-form
元素中设置外部几何形状和位置,而 search-form
块本身仍然是通用的,因为它没有指定任何填充。这种方法允许你创建具有不同行为和样式的组件,同时仍然可以将它们嵌套在其他环境中,因为它们的代码结构是独立的。这有助于提高代码的可复用性和可维护性。