BEM 命名规范深度解析:让 CSS 类名自我表达
BEM 是前端开发中最实用的 CSS 命名规范之一。本文从命名之痛出发,深入剖析 BEM 的三大核心概念,结合微信按钮实战案例,带你彻底掌握这套国际通用的命名体系。
前言
每个前端开发者都经历过这样的痛苦:
.box { }
.box-title { }
.box-content { }
.box-content-item { }
.box-content-item-active { }
类名越来越长,结构越来越乱,维护越来越困难。更糟糕的是,团队协作时每个人有自己的命名习惯,代码风格千差万别。
BEM 就是解决这个问题的银弹。它简单、直观、国际通用,是前端工程化的基石之一。
一、为什么需要 BEM?
1.1 没有规范的命名灾难
假设你在写一个文章列表页面:
<!-- 开发者 A 的写法 -->
<div class="article-list">
<div class="item">
<div class="title">标题</div>
<div class="desc">描述</div>
</div>
</div>
<!-- 开发者 B 的写法 -->
<div class="articles">
<div class="article-item">
<h2 class="article-title">标题</h2>
<p class="article-desc">描述</p>
</div>
</div>
<!-- 开发者 C 的写法 -->
<div class="list">
<div class="list-item">
<div class="list-item-title">标题</div>
<div class="list-item-desc">描述</div>
</div>
</div>
同一个页面,三种完全不同的命名风格。没有规范,代码就是一团乱麻。
1.2 BEM 的核心理念
BEM 让类名即结构。看到类名,就能知道它在页面中的位置和作用:
<div class="article">
<div class="article__item">
<h2 class="article__title">标题</h2>
<p class="article__desc">描述</p>
</div>
</div>
一眼就能看出:article 是块,article__item 是元素,article__title 也是元素。
二、BEM 的三大核心概念
BEM 是 Block(块)、Element(元素)、Modifier(修饰符) 的缩写。
2.1 Block:独立的块
Block 是一个独立的、可复用的组件。它可以是一个按钮、一个导航栏、一个卡片,甚至是一个完整的页面。
特征:
- 独立存在,不依赖其他元素
- 可以在页面任何地方复用
- 命名用简单的英文单词
<!-- Block:按钮 -->
<button class="btn">点击</button>
<!-- Block:卡片 -->
<div class="card">
<img class="card__img" src="...">
<h3 class="card__title">标题</h3>
<p class="card__desc">描述</p>
</div>
<!-- Block:页面 -->
<div class="page">
<header class="page__hd">...</header>
<main class="page__bd">...</main>
</div>
2.2 Element:块的元素
Element 是 Block 的一部分,不能独立存在。它用 双下划线 __ 与 Block 连接。
特征:
- 属于某个 Block,不能单独使用
- 命名格式:
block__element
<div class="card"> <!-- Block -->
<img class="card__img"> <!-- Element -->
<h3 class="card__title"> <!-- Element -->
<span class="card__tag">标签</span> <!-- Element -->
</h3>
<p class="card__desc">...</p> <!-- Element -->
</div>
常见错误:
<!-- ❌ 错误:Element 命名嵌套 -->
<div class="card">
<div class="card__header">
<h3 class="card__header__title">标题</h3> <!-- 错误!命名中出现了 __ __ -->
</div>
</div>
<!-- ✅ 正确:Element 直接属于 Block -->
<div class="card">
<div class="card__header">
<h3 class="card__title">标题</h3> <!-- 正确 -->
</div>
</div>
BEM 命名规范不允许
__嵌套__。所有 Element 都是 Block 的直接子元素,层级通过命名体现,而不是通过__嵌套。
2.3 Modifier:修饰符
Modifier 表示 Block 或 Element 的状态、变体或外观。它用 双中划线 -- 连接。
特征:
- 表示某种状态(禁用、选中、激活)
- 表示某种变体(大小、颜色、主题)
- 命名格式:
block--modifier或block__element--modifier
<!-- Modifier:按钮的不同状态 -->
<button class="btn btn--primary">主要按钮</button>
<button class="btn btn--default">次要按钮</button>
<button class="btn btn--disabled">禁用按钮</button>
<button class="btn btn--loading">加载中</button>
<!-- Modifier:卡片的不同大小 -->
<div class="card card--large">大卡片</div>
<div class="card card--small">小卡片</div>
Modifier 与 Block 同时出现:
Modifier 不能单独使用,必须与 Block 或 Element 一起:
<!-- ❌ 错误:Modifier 单独使用 -->
<button class="btn--primary">点击</button>
<!-- ✅ 正确:Modifier 配合 Block -->
<button class="btn btn--primary">点击</button>
三、标准 BEM vs 微信命名风格:一个重要区分
在深入实战之前,必须先澄清一个常见的混淆点。
3.1 标准 BEM 的符号规则
| 符号 | 标准 BEM | 用途 |
|---|---|---|
__ | ✅ 双下划线 | 连接 Block 和 Element |
-- | ✅ 双中划线 | 连接 Block/Element 和 Modifier |
标准 BEM 示例:
<button class="btn btn--primary">主要按钮</button>
<button class="btn btn--default">次要按钮</button>
.btn--primary { background-color: #07c160; }
.btn--default { background-color: #f0f0f0; }
3.2 微信的命名风格:单下划线 _
微信团队在实际项目中使用的是单下划线 _,而不是标准 BEM 的双中划线 --:
<!-- 微信实际代码 -->
<a class="weui-btn weui-btn_primary">主要按钮</a>
<a class="weui-btn weui-btn_default">次要按钮</a>
/* 微信实际 CSS */
.weui-btn_primary { background-color: #07c160; }
.weui-btn_default { background-color: rgba(0, 0, 0, 0.1); }
3.3 为什么会有这种差异?
| 对比项 | 标准 BEM | 微信风格 |
|---|---|---|
| Element | block__element | block__element ✅ 一致 |
| Modifier | block--modifier | block_modifier ❌ 不同 |
| 历史原因 | Yandex 提出 | 微信团队早期约定 |
| 兼容性 | 现代浏览器 | 早期兼容 IE 的考虑 |
关键点:
__部分两者是一致的,差异只在 Modifier 的符号上。微信用_,标准 BEM 用--。
3.4 你应该用哪种?
推荐:遵循标准 BEM(--)
原因:
- 国际通用:标准 BEM 是全球前端社区广泛认可的规范
- 语义明确:
--一眼就能看出是 Modifier,_容易和 Element 混淆 - 工具支持:大多数 CSS 预处理器和代码检查工具默认支持标准 BEM
- 团队共识:新成员更容易理解
但在微信生态开发时:如果团队已经使用微信风格(_),保持一致性更重要。
3.5 实战:用标准 BEM 重写微信按钮
<!-- 标准 BEM 写法 -->
<div class="page">
<header class="page__hd">
<h1 class="page__title">这是一个页面</h1>
<p class="page__desc">这是一个页面的描述</p>
</header>
<main class="page__bd">
<div class="button-sp-area">
<a class="weui-btn weui-btn--primary">主要按钮</a>
<a class="weui-btn weui-btn--default">次要按钮</a>
<a class="weui-btn weui-btn--default">次要按钮</a>
</div>
</main>
</div>
/* Block:页面 */
.page {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
/* Element:页面头部 */
.page__hd {
padding: 40px;
}
/* Element:页面标题 */
.page__title {
font-size: 20px;
font-weight: 400;
}
/* Element:页面描述 */
.page__desc {
margin-top: 4px;
color: rgba(0, 0, 0, 0.45);
font-size: 14px;
}
/* Block:按钮 */
.weui-btn {
display: block;
width: 184px;
margin: 0 auto;
padding: 12px 24px;
font-size: 17px;
text-align: center;
border-radius: 8px;
}
/* Modifier:主要按钮(标准 BEM:--) */
.weui-btn--primary {
background-color: #07c160;
color: #fff;
}
/* Modifier:次要按钮(标准 BEM:--) */
.weui-btn--default {
color: rgba(0, 0, 0, 0.9);
background-color: rgba(0, 0, 0, 0.1);
}
3.6 命名设计思路
为什么选择 page__hd 而不是 page__header?
BEM 推荐使用简短的缩写,保持命名简洁:
| 完整单词 | BEM 缩写 | 说明 |
|---|---|---|
| header | hd | 头部 |
| body | bd | 主体 |
| footer | ft | 底部 |
| title | title | 标题(已够短) |
| description | desc | 描述 |
为什么选择 weui-btn 而不是 btn?
weui 是项目前缀,表示这个按钮属于微信的 UI 规范。在大型项目中,前缀可以避免命名冲突:
.weui-btn { } /* 微信按钮 */
.ant-btn { } /* Ant Design 按钮 */
.el-btn { } /* Element UI 按钮 */
四、BEM 的四大优势
4.1 结构清晰
看到类名就知道层级关系:
<div class="article">
<div class="article__header">
<h1 class="article__title">标题</h1>
<span class="article__date">2024-01-01</span>
</div>
<div class="article__body">
<p class="article__paragraph">正文...</p>
</div>
<div class="article__footer">
<button class="article__btn article__btn--share">分享</button>
<button class="article__btn article__btn--like">点赞</button>
</div>
</div>
不需要看 HTML 结构,只看类名就能画出页面结构图。
4.2 命名简单
不需要想复杂的英文单词,结构即命名:
/* ❌ 没有 BEM:命名越来越长 */
.page-header { }
.page-header-title { }
.page-header-description { }
.page-content { }
.page-content-button { }
.page-content-button-primary { }
/* ✅ 使用 BEM:命名简洁清晰 */
.page { }
.page__hd { }
.page__title { }
.page__desc { }
.page__bd { }
.btn { }
.btn--primary { }
4.3 无嵌套依赖
BEM 的类名是扁平的,不依赖 HTML 嵌套结构:
/* ❌ 传统嵌套:依赖 HTML 结构 */
.card .title { } /* 只有 .card 里的 .title 才生效 */
.card .content p { } /* 层级太深,性能差 */
/* ✅ BEM:扁平命名,无嵌套依赖 */
.card__title { } /* 任何地方的 .card__title 都生效 */
.card__content { } /* 扁平,性能更好 */
4.4 团队协作
BEM 是国际规范,所有人都能理解:
<!-- 新人看到这段代码,立刻明白结构 -->
<div class="nav">
<a class="nav__item nav__item--active">首页</a>
<a class="nav__item">产品</a>
<a class="nav__item">关于</a>
</div>
不需要注释,类名本身就是最好的注释。
五、BEM 常见误区
5.1 误区一:Element 嵌套 Element
<!-- ❌ 错误 -->
<div class="card">
<div class="card__header">
<h3 class="card__header__title">标题</h3>
</div>
</div>
<!-- ✅ 正确 -->
<div class="card">
<div class="card__header">
<h3 class="card__title">标题</h3>
</div>
</div>
5.2 误区二:Modifier 单独使用
<!-- ❌ 错误 -->
<button class="btn--primary">点击</button>
<!-- ✅ 正确 -->
<button class="btn btn--primary">点击</button>
5.3 误区三:用 Element 表示嵌套层级
<!-- ❌ 错误:试图用命名表示层级 -->
<div class="page">
<div class="page__content">
<div class="page__content__sidebar">
<div class="page__content__sidebar__menu">...</div>
</div>
</div>
</div>
<!-- ✅ 正确:每个都是独立的 Block -->
<div class="page">
<div class="page__content">
<aside class="sidebar">
<nav class="menu">...</nav>
</aside>
</div>
</div>
当结构变得复杂时,应该拆分为多个独立的 Block,而不是用 Element 嵌套。
六、BEM 与 CSS 预处理器
6.1 SCSS 中的 BEM
SCSS 的嵌套语法让 BEM 写起来更优雅:
// Block
.card {
padding: 20px;
border-radius: 8px;
// Element
&__img {
width: 100%;
height: 200px;
}
&__title {
font-size: 18px;
font-weight: bold;
}
&__desc {
color: #666;
}
// Modifier
&--large {
padding: 40px;
}
&--small {
padding: 10px;
}
}
编译后:
.card { padding: 20px; border-radius: 8px; }
.card__img { width: 100%; height: 200px; }
.card__title { font-size: 18px; font-weight: bold; }
.card__desc { color: #666; }
.card--large { padding: 40px; }
.card--small { padding: 10px; }
6.2 避免过度嵌套
// ❌ 过度嵌套:编译后选择器太长
.card {
.card__header {
.card__title {
.card__icon { }
}
}
}
// ✅ 扁平嵌套:保持 BEM 的简洁
.card {
&__header { }
&__title { }
&__icon { }
}
七、BEM 实战:构建一个完整的组件
7.1 需求:一个可复用的卡片组件
<!-- 基础卡片 -->
<div class="card">
<img class="card__img" src="avatar.jpg">
<div class="card__body">
<h3 class="card__title">文章标题</h3>
<p class="card__desc">文章描述...</p>
</div>
<div class="card__footer">
<span class="card__date">2024-01-01</span>
<button class="card__btn card__btn--primary">阅读更多</button>
</div>
</div>
<!-- 大图卡片 -->
<div class="card card--large">
<img class="card__img" src="banner.jpg">
<div class="card__body">
<h3 class="card__title">大图文章标题</h3>
<p class="card__desc">大图文章描述...</p>
</div>
</div>
<!-- 横向卡片 -->
<div class="card card--horizontal">
<img class="card__img" src="thumb.jpg">
<div class="card__body">
<h3 class="card__title">横向文章标题</h3>
<p class="card__desc">横向文章描述...</p>
</div>
</div>
7.2 CSS 实现
/* Block:卡片 */
.card {
display: flex;
flex-direction: column;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* Element:图片 */
.card__img {
width: 100%;
height: 200px;
object-fit: cover;
}
/* Element:内容区 */
.card__body {
padding: 16px;
}
/* Element:标题 */
.card__title {
font-size: 18px;
font-weight: bold;
margin-bottom: 8px;
}
/* Element:描述 */
.card__desc {
font-size: 14px;
color: #666;
line-height: 1.5;
}
/* Element:底部 */
.card__footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
border-top: 1px solid #eee;
}
/* Element:按钮 */
.card__btn {
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
}
/* Modifier:主要按钮 */
.card__btn--primary {
background-color: #07c160;
color: #fff;
}
/* Modifier:次要按钮 */
.card__btn--default {
background-color: #f0f0f0;
color: #333;
}
/* Modifier:大图卡片 */
.card--large .card__img {
height: 300px;
}
.card--large .card__title {
font-size: 24px;
}
/* Modifier:横向卡片 */
.card--horizontal {
flex-direction: row;
}
.card--horizontal .card__img {
width: 120px;
height: 120px;
}
八、BEM 命名速查表
| 类型 | 格式 | 示例 | 说明 |
|---|---|---|---|
| Block | .block | .btn、.card、.nav | 独立组件 |
| Element | .block__element | .btn__icon、.card__title | 组件的一部分 |
| Modifier | .block--modifier | .btn--primary、.card--large | 状态或变体 |
| Element + Modifier | .block__element--modifier | .nav__item--active | 元素的状态 |
九、总结
BEM 是前端开发中最简单、最实用的命名规范:
- Block:独立的组件,用简单单词命名
- Element:组件的一部分,用
__连接 - Modifier:状态或变体,用
--连接 - 扁平结构:不嵌套 Element,保持类名简洁
- 团队协作:国际规范,所有人都能理解
掌握 BEM,你的 CSS 类名将自我表达,代码将清晰可维护,团队协作将高效顺畅。
命名是编程中最难的事情之一。BEM 让命名变得简单,让代码变得优雅。
标签:CSS BEM 命名规范 前端工程化 最佳实践 微信UI