Skip to content

BEM 命名规范:CSS类名的组织艺术

引言

在大型网站和应用程序的开发过程中,CSS的维护常常成为一个巨大的挑战。随着项目规模的扩大,样式表变得越来越复杂,类名重复、样式覆盖、选择器嵌套过深等问题接踵而至。为了解决这些问题,我们需要一种结构化的方法来组织CSS代码,而BEM命名规范就是这样一种解决方案。

BEM是Block(块)、Element(元素)和Modifier(修饰符)的缩写,它提供了一种命名CSS类的规则,旨在使CSS代码更加结构化、可预测和可维护。采用BEM方法,可以创建组件化的CSS,减少样式冲突,并提高代码的可读性和可重用性。

本文将详细介绍BEM命名规范的概念、语法和应用,帮助你在项目中有效地实施这一方法论。

BEM 基本概念:三大核心元素

BEM方法论将用户界面分解为独立的块,使开发更加简单和快速。它核心思想是将用户界面划分为三种不同类型的实体:

1. Block(块)

Block是一个独立的组件,可以在页面上的任何地方重用。它是一个逻辑上和功能上独立的页面组件,类似于Web组件中的"组件"概念。每个Block都应该设计成既能独立存在,又能被嵌套和重用。

特点

  • 功能独立,不依赖页面上的其他块
  • 可以嵌套在其他块中
  • 可以在页面的不同位置重复使用

示例:导航菜单、登录表单、按钮、标题、卡片等。

2. Element(元素)

Element是Block的组成部分,不能脱离Block独立使用。Element是依赖于Block存在的,它们共同构成了Block的结构。

特点

  • 语义上从属于Block
  • 只能作为Block的一部分存在
  • 不应该脱离Block使用

示例:导航链接(作为导航菜单的一部分)、表单输入框、卡片标题等。

3. Modifier(修饰符)

Modifier用于表示Block或Element的状态、属性或变体。它们允许你创建类似于Block或Element的视觉或行为变化,而无需创建新的Block或Element。

特点

  • 表示Block或Element的外观、状态或行为
  • 类似于HTML中的布尔属性或带值属性
  • 不能单独使用,必须附加到Block或Element上

示例:禁用状态的按钮、活动状态的导航项、尺寸变体(大、中、小)等。

BEM 命名规则:语法与约定

BEM采用特定的命名约定来明确表示这三种实体之间的关系。

基本语法

.block {}
.block__element {}
.block--modifier {}
.block__element--modifier {}

具体规则如下:

1. Block 命名

Block的命名应该简明扼要地描述其目的,而不是外观。

css
/* 正确 */
.button {}
.menu {}
.header {}

/* 错误 - 描述了外观而非目的 */
.red-text {}
.big-blue-box {}

2. Element 命名

Element通过双下划线(__)连接到其Block名称,表示它是Block的一部分。

css
.menu__item {}
.header__logo {}
.form__input {}
.form__submit-button {}

3. Modifier 命名

Modifier通过双连字符(--)连接到Block或Element,表示这是一个修改版本。

css
/* Block修饰符 */
.button--primary {}
.button--large {}

/* Element修饰符 */
.menu__item--active {}
.form__input--disabled {}

Modifier有两种类型:

  1. 布尔型:表示存在/不存在某种状态(如activedisabled

    css
    .button--disabled {}
  2. 键值型:指定一个特定值(如size-largetheme-dark

    css
    .button--size-large {}
    .menu--theme-dark {}

4. 混合使用

当需要同时应用多个修饰符时,每个修饰符都作为单独的类名添加:

html
<button class="button button--large button--primary">
  提交
</button>

BEM 实际应用示例

了解了BEM的基本概念和命名规则后,让我们通过一些具体的示例来看看如何在实际项目中应用BEM。

示例1:导航菜单

html
<nav class="main-nav">
  <ul class="main-nav__list">
    <li class="main-nav__item">
      <a href="#" class="main-nav__link main-nav__link--active">首页</a>
    </li>
    <li class="main-nav__item">
      <a href="#" class="main-nav__link">产品</a>
    </li>
    <li class="main-nav__item">
      <a href="#" class="main-nav__link main-nav__link--disabled">关于我们</a>
    </li>
  </ul>
</nav>
css
.main-nav {
  background-color: #f8f9fa;
  padding: 15px;
}

.main-nav__list {
  list-style: none;
  display: flex;
  margin: 0;
  padding: 0;
}

.main-nav__item {
  margin-right: 20px;
}

.main-nav__link {
  text-decoration: none;
  color: #333;
  padding: 5px 10px;
}

.main-nav__link--active {
  color: #007bff;
  font-weight: bold;
}

.main-nav__link--disabled {
  color: #ccc;
  cursor: not-allowed;
}

示例2:卡片组件

html
<article class="card card--featured">
  <header class="card__header">
    <h2 class="card__title">文章标题</h2>
    <p class="card__meta">发布于 2023-04-01</p>
  </header>
  <div class="card__content">
    <p>这是文章的内容...</p>
  </div>
  <footer class="card__footer">
    <button class="card__button card__button--primary">阅读更多</button>
    <button class="card__button">分享</button>
  </footer>
</article>
css
.card {
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 20px;
  margin-bottom: 20px;
}

.card--featured {
  border-color: #007bff;
  box-shadow: 0 2px 4px rgba(0, 123, 255, 0.2);
}

.card__header {
  margin-bottom: 15px;
}

.card__title {
  margin: 0 0 5px;
  font-size: 1.5rem;
}

.card__meta {
  color: #666;
  font-size: 0.875rem;
  margin: 0;
}

.card__content {
  margin-bottom: 15px;
}

.card__footer {
  display: flex;
  justify-content: flex-end;
}

.card__button {
  background: none;
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 8px 12px;
  margin-left: 10px;
  cursor: pointer;
}

.card__button--primary {
  background-color: #007bff;
  border-color: #007bff;
  color: white;
}

示例3:表单组件

html
<form class="form">
  <div class="form__group">
    <label class="form__label" for="username">用户名</label>
    <input class="form__input" id="username" type="text" />
    <span class="form__hint">请输入您的用户名</span>
  </div>
  
  <div class="form__group">
    <label class="form__label" for="password">密码</label>
    <input class="form__input form__input--error" id="password" type="password" />
    <span class="form__error">密码必须包含字母和数字</span>
  </div>
  
  <div class="form__actions">
    <button class="form__button form__button--submit">登录</button>
    <button class="form__button form__button--cancel">取消</button>
  </div>
</form>
css
.form {
  max-width: 400px;
  margin: 0 auto;
}

.form__group {
  margin-bottom: 20px;
}

.form__label {
  display: block;
  margin-bottom: 5px;
  font-weight: bold;
}

.form__input {
  width: 100%;
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.form__input--error {
  border-color: #dc3545;
}

.form__hint {
  display: block;
  font-size: 0.875rem;
  color: #666;
  margin-top: 5px;
}

.form__error {
  display: block;
  font-size: 0.875rem;
  color: #dc3545;
  margin-top: 5px;
}

.form__actions {
  display: flex;
  justify-content: flex-end;
}

.form__button {
  padding: 10px 15px;
  border: none;
  border-radius: 4px;
  margin-left: 10px;
  cursor: pointer;
}

.form__button--submit {
  background-color: #28a745;
  color: white;
}

.form__button--cancel {
  background-color: #6c757d;
  color: white;
}

BEM 最佳实践与常见问题

在实际项目中应用BEM时,以下最佳实践可以帮助你更有效地使用这种命名规范:

最佳实践

1. 保持扁平结构

BEM旨在创建扁平的CSS选择器层次结构,避免过深的嵌套。一般来说,BEM选择器不应该超过1-2级深度。

css
/* 好的做法 */
.block {}
.block__element {}

/* 避免这样做 */
.block .block__element .block__element-child {}

2. 关注组件的职责

每个Block应该负责自己的样式和行为,而不影响其他Block。这促进了组件的独立性和可重用性。

3. 避免在HTML中过度嵌套

BEM的命名约定已经表明了元素之间的关系,因此不需要在HTML中严格反映这种嵌套关系。

html
<!-- 好的做法 -->
<div class="card">
  <h2 class="card__title">标题</h2>
  <div class="card__content">
    <p class="card__text">内容</p>
  </div>
</div>

<!-- 也是可以的,尽管HTML结构不完全匹配BEM命名 -->
<div class="card">
  <h2 class="card__title">标题</h2>
  <p class="card__text">内容</p>
</div>

4. 组合Block而不是创建复杂的Element链

当你发现自己创建了很长的Element名称(如.block__element1__element2),这通常意味着你应该创建一个新的Block,而不是深度嵌套的Element。

css
/* 避免这样做 */
.card__header__title {}
.card__footer__button__icon {}

/* 更好的做法 */
.card__title {}
.card-button__icon {}

5. 使用混合(Mix)而不是嵌套

当需要在一个Block内部使用另一个Block时,使用"混合"方法:

html
<div class="card">
  <button class="button card__button">点击我</button>
</div>

这里,元素同时属于两个不同的Block(buttoncard),但作为一个Element存在于card中。

常见问题与解决方案

问题1:类名过长

BEM类名可能会变得很长,特别是对于复杂组件。

解决方案

  • 使用简洁的Block名称
  • 在适当的时候创建新的Block而不是深度嵌套的Element
  • 考虑使用预处理器(如SASS/SCSS)来简化书写

问题2:何时创建新Block vs. 使用Element

有时很难决定是创建新的Block还是使用现有Block的Element。

解决方案:问自己这个组件是否可以在其他上下文中独立存在?如果可以,它应该是一个Block;如果不能,它可能是一个Element。

问题3:处理状态变化

解决方案

  • 使用布尔型Modifier来表示状态(如--active--disabled
  • 对于基于JavaScript的状态变化,可以动态添加和移除Modifier类

问题4:与现有代码库集成

解决方案

  • 逐步采用BEM,先应用于新组件
  • 使用命名空间来隔离BEM代码和传统代码
  • 考虑使用CSS作用域解决方案(如CSS Modules或Styled Components)来隔离不同命名规范的代码

BEM 与其他CSS方法论的比较

为了全面了解BEM的优缺点,我们来简单比较一下BEM与其他流行的CSS方法论:

BEM vs. OOCSS(面向对象的CSS)

OOCSS强调将结构和皮肤分离,以及将容器和内容分离。

主要区别

  • OOCSS更关注原则,而BEM提供了具体的命名约定
  • BEM的类名更明确地表示组件关系
  • OOCSS强调可重用性,而BEM强调组件的独立性

BEM vs. SMACSS(可扩展和模块化的CSS架构)

SMACSS将CSS分为基础、布局、模块、状态和主题五个类别。

主要区别

  • SMACSS提供了更广泛的项目组织指南
  • BEM主要关注命名约定
  • SMACSS使用不同的前缀(如l-表示布局,is-表示状态),而BEM使用特定的连接符(__--

BEM vs. Atomic CSS

Atomic CSS使用单一用途的类名,每个类只做一件事。

主要区别

  • Atomic CSS类名基于功能(如.mt-10表示margin-top: 10px
  • BEM类名基于组件和语义
  • Atomic CSS导致HTML中有更多的类名,但CSS文件更小
  • BEM导致CSS文件更大,但HTML更干净

总结

BEM命名规范是一种强大的CSS组织方法,通过明确的命名约定来表示组件之间的关系。它的主要优势包括:

  • 减少样式冲突:通过具体和唯一的类名减少CSS选择器冲突的风险
  • 提高可读性:仅通过类名就能理解元素的结构和关系
  • 增强可维护性:模块化结构使得添加、修改和删除组件变得更加容易
  • 促进重用:独立的块可以在不同的上下文中重复使用

虽然BEM可能会导致较长的类名,但其带来的结构清晰和维护便利通常远超这一微小的缺点。对于中大型项目,BEM特别有价值,它可以帮助团队成员轻松理解和修改彼此的代码。

总之,BEM不仅仅是一种命名约定,它是一种思考和组织用户界面的方法,能够帮助开发者创建更加模块化、可维护的CSS代码。

拓展阅读

  1. BEM官方网站
  2. CSS架构的8个简单规则
  3. BEM命名规范详解
  4. 在大型项目中使用BEM
  5. CSS方法论比较

用❤️分享前端技术 fedev.wiki