一、什么是 BEM?
BEM(Block Element Modifier)是一种 CSS 命名方法论,旨在通过结构化的命名规则提升代码的 可维护性、可读性和模块化。它由 Yandex 团队提出,核心思想是将界面拆分为 独立的模块(Block),并通过 元素(Element) 和 修饰符(Modifier) 来描述模块的组成和状态变化。
二、BEM 的核心概念BEM 的命名规则由三个部分组成:
- Block(块)
- 定义:一个独立的功能单元或组件,可以是按钮、导航栏、卡片等。
- 命名规则:
- 使用名词描述功能(如
.card
,.menu
)。 - 多个单词用连字符(
-
)连接(如.user-profile
)。
- 使用名词描述功能(如
- 示例:
.card { /* 独立的卡片组件 */ }
- Element(元素)
- 定义:块的组成部分,不能独立存在,必须依赖于所属的块。
- 命名规则:
- 使用双下划线
__
连接块名和元素名(如.card__title
,.menu__item
)。 - 元素名通常用名词描述其角色(如
.card__image
,.card__description
)。
- 使用双下划线
- 示例:
.card__title { /* 卡片的标题元素 */ }
- Modifier(修饰符)
- 定义:用于描述块或元素的状态或变体(如激活、禁用、颜色主题)。
- 命名规则:
- 使用双连字符
--
连接块名或元素名与修饰符名(如.card--dark
,.card__button--primary
)。 - 修饰符名通常用形容词描述状态(如
.card--disabled
,.card__button--large
)。
- 使用双连字符
- 示例:
.card--dark { /* 卡片的暗色主题变体 */ }
三、BEM 命名规则总结
类型 | 命名格式 | 示例 |
---|---|---|
Block | .block-name | .card |
Element | .block-name__element-name | .card__title |
Modifier(块) | .block-name--modifier-name | .card--dark |
Modifier(元素) | .block-name__element-name--modifier-name | .card__button--primary |
四、BEM 的优势
- 样式零冲突
- 通过模块化命名(如
.pagination__prev
),天然隔离组件内外样式,避免全局污染。
- 通过模块化命名(如
- 代码自解释
- 类名直接描述模块、元素和状态(如
.user-card__avatar--rounded
),无需注释即可理解代码含义。
- 类名直接描述模块、元素和状态(如
- 维护性高
- 直接通过类名定位元素,无需查找 DOM 结构,减少调试成本。
- 团队协作友好
- 统一的命名规则让新人快速理解代码结构,降低协作成本。
五、BEM 实践示例
1. 正确示范:分页组件
<div class="pagination">
<button class="pagination__prev">上一页</button>
<span class="pagination__page pagination__page--active">1</span>
<button class="pagination__next">下一页</button></div>
.pagination { /* 分页模块样式 */ }
.pagination__prev { /* 上一页按钮样式 */ }
.pagination__page--active { /* 当前页样式 */ }
2. 错误示范:多层嵌套命名
<div class="pagination">
<ul class="pagination__list">
<li class="pagination__list__item"> <!-- 违反 BEM 扁平化原则! --> </li>
</ul>
</div>
问题:
pagination__list__item
是三层嵌套,不符合 BEM 的扁平化命名原则。应改为.pagination__item
。
六、BEM 的实战避坑指南
- 避免使用子代选择器
/* 危险!样式与全局 .btn 耦合 */ .popup .btn { ... } /* 正确!模块化命名 */ .popup__confirm-btn { ... }
- 保持命名一致性 - 同一个项目中,所有模块、元素和修饰符需遵循统一的命名规则。 - 避免混用 BEM 与其他命名方式(如
.my-button
vs.card__button
)。3. 修饰符优先级 - 修饰符应直接作用于块或元素,而非嵌套层级。
/* 正确 */ .card--dark { ... } /* 错误:修饰符嵌套在元素中 */ .card .card__title--dark { ... }
七、BEM 在大型项目中的应用以腾讯 WEUI 框架为例:
<div class="page">
<div class="page__hd">头部</div>
<div class="page__bd">主体内容</div>
<div class="page__ft">底部</div>
</div>
.page { /* 页面根容器 */ }
.page__hd { /* 头部区域 */ }
.page__bd { /* 主体内容区域 */ }
.page__ft { /* 底部区域 */ }
八、BEM 与现代前端框架的结合BEM 的模块化思想与 React、Vue 等组件化框架天然契合:
// React 示例
function Card({ title, content, isDark }) {
return (
<div className={`card ${isDark ? 'card--dark' : ''}`}>
<h2 className="card__title">{title}</h2>
<p className="card__content">{content}</p>
<button className="card__button card__button--primary">提交</button>
</div>
);
}
九、BEM 的局限性
- 类名较长:相比扁平化命名(如
.btn
),BEM 的类名可能更长(如.card__title--rounded
)。 - 学习成本:需要开发者理解模块化思想和命名规则。
- 过度设计风险:简单项目中可能显得繁琐,但大型项目中优势显著。
十、总结BEM 命名规范通过 模块化、扁平化 的命名规则,解决了传统 CSS 开发中的 样式冲突、维护困难 等问题。掌握 BEM 不仅能提升代码质量,还能为团队协作和项目扩展奠定基础。在实际开发中,建议结合工具(如 PostCSS、CSS 预处理器)自动化实现 BEM,并遵循以下原则:
- 块独立:每个块应是独立的、可复用的组件。
- 元素扁平化:元素命名不嵌套层级。
- 修饰符明确:修饰符直接描述状态或外观变化。