MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

CSS BEM命名规范在大型项目中的实践价值

2022-12-101.2k 阅读

CSS BEM命名规范基础概念

BEM 是什么

BEM 是 “块(Block)、元素(Element)、修饰符(Modifier)” 的缩写,是由 Yandex 团队提出的一种前端 CSS 命名方法论。这种命名规范旨在创建一种易于理解、可维护且具有扩展性的 CSS 类名结构,尤其适用于大型项目。

块(Block)是一个独立的实体,在页面中具有明确的语义和功能。例如,一个导航栏(nav)、一个按钮(button)都可以看作是一个块。块在页面布局和功能上是相对独立的单元,它应该可以在不同的上下文中复用。

元素(Element)是块的组成部分,不能脱离块单独存在。比如,导航栏中的菜单项(nav__item),按钮中的图标(button__icon)就是元素。元素通常是块中实现具体功能或展示具体内容的部分。

修饰符(Modifier)用于定义块或元素的外观、状态或行为的变化。例如,一个按钮可能有不同的颜色状态(button--primarybutton--secondary),一个输入框可能有禁用状态(input--disabled),这些就是修饰符。

BEM 命名格式

BEM 的命名格式非常直观。块的命名通常是一个单词或多个单词组成的字符串,例如 headerproduct-card

元素的命名是在块名后面加上双下划线(__),然后再接元素名,例如 header__logoproduct-card__image

修饰符的命名是在块名或元素名后面加上双连字符(--),再接修饰符名,例如 button--disabledproduct-card__image--rounded

在大型项目中使用 BEM 命名规范的优势

提高代码的可维护性

在大型项目中,代码库往往非常庞大,涉及到众多的开发人员。如果没有一个统一的命名规范,CSS 类名可能会变得混乱不堪。例如,不同的开发人员可能会用不同的方式命名相同功能的元素,这就导致在修改或添加功能时,很难快速定位到相关的 CSS 代码。

使用 BEM 命名规范后,类名具有清晰的结构。比如,在一个电商项目中,如果要修改商品卡片的样式,通过 product-card 这个块名,就可以很容易找到所有与商品卡片相关的 CSS 代码。如果是要修改商品卡片中的图片样式,product-card__image 这个类名就明确指向了该元素的样式。即使项目规模不断扩大,新加入的开发人员也能快速理解代码结构,进行维护工作。

增强代码的复用性

块作为独立的实体,很容易在不同的页面或项目部分复用。例如,在一个网站中,可能有多个地方需要用到按钮,使用 BEM 命名规范,定义一个 button 块,在不同的地方引用这个块的 CSS 代码即可。而且,由于元素和修饰符的存在,还可以灵活地定制复用块的具体样式。比如,在一个表单中有提交按钮和取消按钮,它们都复用 button 块的基本样式,通过修饰符 button--submitbutton--cancel 来定义各自不同的样式,如颜色、大小等。

方便团队协作

在大型团队开发中,不同的开发人员可能负责不同的模块。BEM 命名规范提供了一种通用的语言,使得团队成员之间能够更好地沟通和协作。当讨论某个功能的样式时,通过 BEM 类名就能清楚地知道是哪个块、元素或修饰符的样式。例如,当一个开发人员说要修改 product-card__price--discounted 的样式时,其他成员马上就能明白是商品卡片中打折价格的样式,无需额外的解释。

BEM 命名规范在 HTML 与 CSS 中的实践

HTML 中的 BEM 应用

在 HTML 中使用 BEM 命名规范,就是将 BEM 类名应用到相应的 HTML 元素上。以一个简单的导航栏为例:

<nav class="nav">
  <a href="#" class="nav__item">首页</a>
  <a href="#" class="nav__item">产品</a>
  <a href="#" class="nav__item nav__item--active">关于我们</a>
</nav>

在这个例子中,nav 是块,nav__item 是块 nav 的元素,nav__item--active 是元素 nav__item 的修饰符,表示当前激活的菜单项。这样的命名方式使得 HTML 结构与 CSS 命名紧密结合,一目了然。

CSS 中的 BEM 样式编写

在 CSS 中,针对 BEM 类名编写样式也很直观。继续以上面的导航栏为例:

.nav {
  background-color: #333;
  color: white;
  padding: 10px;
}

.nav__item {
  display: inline-block;
  margin-right: 20px;
  text-decoration: none;
  color: white;
}

.nav__item--active {
  text-decoration: underline;
}

这里,nav 块定义了导航栏整体的背景颜色、文本颜色和内边距。nav__item 元素定义了菜单项的基本样式,如显示为行内块元素、设置右边距和文本装饰等。nav__item--active 修饰符则针对激活状态的菜单项添加了下划线样式。

处理复杂布局与嵌套结构

多层嵌套中的 BEM 应用

在实际项目中,布局往往比较复杂,存在多层嵌套的情况。例如,一个电商产品详情页,可能有一个 product-detail 块,里面包含 product-infoproduct-images 等子块,product-info 块又包含 product-titleproduct-price 等元素。

<div class="product-detail">
  <div class="product-detail__product-info">
    <h1 class="product-detail__product-info__product-title">商品标题</h1>
    <span class="product-detail__product-info__product-price">价格:199元</span>
  </div>
  <div class="product-detail__product-images">
    <img src="image1.jpg" class="product-detail__product-images__image">
    <img src="image2.jpg" class="product-detail__product-images__image">
  </div>
</div>

在 CSS 中:

.product-detail {
  display: flex;
  justify-content: space-between;
}

.product-detail__product-info {
  width: 60%;
}

.product-detail__product-info__product-title {
  font-size: 24px;
  margin-bottom: 10px;
}

.product-detail__product-info__product-price {
  font-size: 18px;
  color: #ff5722;
}

.product-detail__product-images {
  width: 40%;
}

.product-detail__product-images__image {
  width: 100%;
  margin-bottom: 10px;
}

通过这种方式,即使嵌套层次较多,也能保持清晰的命名和样式逻辑。

避免过度嵌套带来的问题

虽然 BEM 能够处理多层嵌套结构,但过度嵌套也会带来一些问题,比如 CSS 选择器变得冗长,影响性能。例如,如果有非常深的嵌套,如 block__element__sub - element__sub - sub - element,不仅类名很长,而且在查找和渲染时也会消耗更多资源。

为了避免这种情况,在设计布局时应尽量保持结构的简洁性。如果发现嵌套过深,可以考虑是否可以将某些部分拆分成独立的块。比如,在一个大型表单中,如果有一个复杂的输入组,包含多个输入框和标签,并且有很多内部嵌套结构,可以将这个输入组拆分成一个独立的 input - group 块,这样可以减少嵌套层次,同时提高代码的复用性。

与其他前端框架的结合使用

与 React 的结合

在 React 项目中使用 BEM 命名规范同样非常有效。React 组件可以看作是 BEM 中的块。例如,创建一个 Button 组件:

import React from'react';

const Button = ({ children, type }) => {
  return (
    <button className={`button button--${type}`}>
      {children}
    </button>
  );
};

export default Button;

在 CSS 中:

.button {
  padding: 10px 20px;
  border: none;
  border - radius: 5px;
  cursor: pointer;
}

.button--primary {
  background-color: #1976d2;
  color: white;
}

.button--secondary {
  background-color: #f5f5f5;
  color: #333;
}

这样,通过 React 组件和 BEM 命名规范的结合,组件的样式管理变得更加清晰,不同类型的按钮通过修饰符来区分样式。

与 Vue 的结合

在 Vue 项目中,也可以很好地应用 BEM 命名规范。例如,创建一个 Card 组件:

<template>
  <div class="card">
    <h2 class="card__title">{{ title }}</h2>
    <p class="card__content">{{ content }}</p>
    <button class="card__button" @click="handleClick">点击我</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      title: '卡片标题',
      content: '卡片内容'
    };
  },
  methods: {
    handleClick() {
      console.log('按钮被点击');
    }
  }
};
</script>

<style scoped>
.card {
  border: 1px solid #ccc;
  border - radius: 5px;
  padding: 15px;
}

.card__title {
  font-size: 18px;
  margin-bottom: 10px;
}

.card__content {
  margin-bottom: 15px;
}

.card__button {
  background-color: #00bcd4;
  color: white;
  border: none;
  padding: 8px 15px;
  border - radius: 3px;
  cursor: pointer;
}
</style>

在这个 Vue 组件中,card 是块,card__titlecard__contentcard__button 是元素,通过 BEM 命名规范,组件的样式和结构一目了然。

性能与优化方面的考量

BEM 对 CSS 加载性能的影响

BEM 命名规范本身并不会直接对 CSS 加载性能产生负面影响。相反,由于其清晰的结构,在维护和扩展 CSS 时更容易进行优化。例如,在大型项目中,如果使用不规范的命名,可能会导致很多冗余的 CSS 代码,增加文件大小,从而影响加载性能。而 BEM 命名规范使得代码结构清晰,开发人员可以更容易地识别和删除无用的样式。

同时,BEM 命名规范使得 CSS 选择器相对简单。简单的选择器在浏览器解析和渲染时效率更高。例如,button--primary 这样的选择器比复杂的层级选择器(如 body div.container button.primary)在性能上更优,因为浏览器在匹配元素时需要遍历的 DOM 节点更少。

优化 BEM 样式以提升性能

虽然 BEM 命名规范有助于保持代码结构清晰,但在编写样式时仍需注意性能优化。例如,尽量避免使用通配符选择器(*),因为它会匹配页面上的所有元素,大大增加浏览器的计算量。在 BEM 样式中,应该精确地选择需要应用样式的块、元素或修饰符。

另外,对于一些重复的样式,可以通过 CSS 变量来进行优化。比如,在一个项目中,按钮的主要颜色在多个地方使用,可以定义一个 CSS 变量:

:root {
  --primary - color: #1976d2;
}

.button--primary {
  background-color: var(--primary - color);
  color: white;
}

.link--primary {
  color: var(--primary - color);
}

这样,如果需要修改主要颜色,只需要在 :root 中修改 --primary - color 的值即可,而不需要在每个使用该颜色的地方进行修改,同时也减少了重复的代码,提升了性能。

实际案例分析

电商项目中的 BEM 应用

以一个电商项目为例,该项目有产品列表页、产品详情页、购物车页面等多个页面。在产品列表页,使用 BEM 命名规范来构建样式。

产品列表可以看作一个块 product - list,每个产品项是 product - list 的元素 product - list__item。产品项中有产品图片 product - list__item__image、产品标题 product - list__item__title、产品价格 product - list__item__price 等元素。如果产品有促销活动,通过修饰符 product - list__item--on - sale 来表示。

<ul class="product - list">
  <li class="product - list__item product - list__item--on - sale">
    <img src="product1.jpg" class="product - list__item__image">
    <h3 class="product - list__item__title">商品 1</h3>
    <span class="product - list__item__price">原价:299元  现价:199元</span>
  </li>
  <li class="product - list__item">
    <img src="product2.jpg" class="product - list__item__image">
    <h3 class="product - list__item__title">商品 2</h3>
    <span class="product - list__item__price">价格:399元</span>
  </li>
</ul>

在 CSS 中:

.product - list {
  list - style: none;
  padding: 0;
  display: flex;
  flex - wrap: wrap;
  justify-content: space - around;
}

.product - list__item {
  width: 300px;
  border: 1px solid #ccc;
  border - radius: 5px;
  padding: 15px;
  margin-bottom: 20px;
}

.product - list__item--on - sale {
  border - color: #ff5722;
}

.product - list__item__image {
  width: 100%;
  height: 200px;
  object - fit: cover;
  margin-bottom: 10px;
}

.product - list__item__title {
  font-size: 18px;
  margin-bottom: 5px;
}

.product - list__item__price {
  font-size: 16px;
  color: #333;
}

通过 BEM 命名规范,产品列表页的样式结构清晰,易于维护和扩展。当需要添加新的产品样式或修改现有样式时,开发人员可以快速定位到相关的 CSS 代码。

社交平台项目中的 BEM 应用

在一个社交平台项目中,用户动态展示部分是一个重要的模块。可以将用户动态块定义为 user - feed,每个动态项是 user - feed__item。动态项中包含用户头像 user - feed__item__avatar、用户名 user - feed__item__username、动态内容 user - feed__item__content、点赞按钮 user - feed__item__like - button 等元素。如果动态被用户点赞,通过修饰符 user - feed__item__like - button--liked 来表示。

<div class="user - feed">
  <div class="user - feed__item">
    <img src="avatar1.jpg" class="user - feed__item__avatar">
    <span class="user - feed__item__username">张三</span>
    <p class="user - feed__item__content">今天天气真好!</p>
    <button class="user - feed__item__like - button user - feed__item__like - button--liked">点赞(10)</button>
  </div>
  <div class="user - feed__item">
    <img src="avatar2.jpg" class="user - feed__item__avatar">
    <span class="user - feed__item__username">李四</span>
    <p class="user - feed__item__content">分享一篇有趣的文章。</p>
    <button class="user - feed__item__like - button">点赞(5)</button>
  </div>
</div>

在 CSS 中:

.user - feed {
  padding: 20px;
}

.user - feed__item {
  border-bottom: 1px solid #eee;
  padding-bottom: 15px;
  margin-bottom: 15px;
}

.user - feed__item__avatar {
  width: 50px;
  height: 50px;
  border - radius: 50%;
  object - fit: cover;
  margin-right: 10px;
  display: inline - block;
  vertical-align: middle;
}

.user - feed__item__username {
  font-weight: bold;
  margin-right: 5px;
}

.user - feed__item__content {
  margin-top: 5px;
}

.user - feed__item__like - button {
  background-color: #f5f5f5;
  border: none;
  padding: 5px 10px;
  border - radius: 3px;
  cursor: pointer;
}

.user - feed__item__like - button--liked {
  background-color: #1976d2;
  color: white;
}

通过这种 BEM 命名方式,社交平台用户动态展示部分的样式管理变得有序,无论是添加新的功能样式还是修改现有样式,都能高效完成。

应对挑战与常见问题解决

团队成员对 BEM 规范的适应问题

在引入 BEM 命名规范初期,团队成员可能对这种新的命名方式不太适应,尤其是习惯了传统命名方式的开发人员。为了解决这个问题,可以组织专门的培训,详细介绍 BEM 的概念、命名规则以及在项目中的实践案例。同时,在项目初期,可以设置代码审查环节,对不符合 BEM 规范的代码进行及时纠正,并给予相应的指导。随着时间的推移,团队成员会逐渐熟悉并接受这种命名规范。

命名冲突问题

虽然 BEM 命名规范通过块、元素和修饰符的结构减少了命名冲突的可能性,但在大型项目中,仍然可能出现冲突。例如,不同模块可能定义了相同名称的块。为了避免这种情况,可以在项目开始时,对各个模块的命名空间进行规划。比如,对于用户相关模块的块名,可以统一加上 user - 前缀,如 user - profileuser - settings;对于产品相关模块的块名,加上 product - 前缀,如 product - listproduct - detail。这样可以有效地防止命名冲突。

与现有项目代码整合问题

如果是在现有项目中引入 BEM 命名规范,可能会面临与现有代码整合的困难。由于现有代码可能采用了不同的命名方式,直接替换所有类名成本较高。一种可行的方法是逐步替换,先从新开发的功能模块开始使用 BEM 命名规范,对于现有模块,可以在需要修改或扩展功能时,将相关的 CSS 代码按照 BEM 规范进行重构。同时,可以使用 CSS 预处理器(如 Sass 或 Less)的混合(Mixin)功能,将现有代码的样式与 BEM 样式进行融合,逐步过渡到完全采用 BEM 命名规范。