编写模块化CSS:BEM

BEM(Block, Element, Modifier)是一种CSS命名规范,旨在通过将界面拆分为独立的区块来简化和加速界面开发。文章详细介绍了BEM的概念、优势,以及Block、Element、Modifier的用法,并提供了实例说明如何创建和使用BEM结构。此外,还讨论了BEM在项目维护和团队协作中的重要性。" 113350078,10546865,宝塔面板MySQL数据恢复指南,"['数据库恢复', 'MySQL管理', '宝塔面板', '数据安全']
摘要由CSDN通过智能技术生成
什么是BEM

BEM其实是块(block),元素(element),修饰符(modifier)的缩写,利用不同的区块,功能以及样式来给元素命名。其目的是将用户界面分割成独立的区块,使得即使有复杂的UI,界面开发也能变得更加简单和快速;而且能使代码变得低耦合和高复用。常用的命名约定模式如下:

.block-name {}
.block-name__element-name {}
.block-name--modifier-name {}

/* 也可以用简短一点的命名方式 */
.blockName {}
.blockName_elementName {}
.blockName-modifierName {}

block代表了更高级别的抽象或组件
block__element 代表 block 的后代,用于形成一个完整的 block 的整体
block--modifier(block__element--modifier)用来限定修饰block(element)的样式,状态或行为
在这里插入图片描述

BEM的优势

BEM的关键是光凭class名字就可以让其他开发者知道某个标记用来做什么的,明白各个模块之间的关系,例如如下的命名:

.nav{}
.nav__item{}
.nav--blue{}

这几个class名很明显能看出各个块的作用,顶级块是nav,它有一些元素比如item,item又有一些属性,例如blue,但是如果写成常规的css就会很难看得出它们的关系:

.nav{}
.item{}
.blue{}

虽然看每一个class名知道它们代表什么,但是却看不出它们之间的关系,这样对比,很明显能看出BEM命名的优势。

Block

block代表了功能上独立的可复用的组件,在HTML中,block用class属性来表示
特性:

  • block的name应该描述它是一个什么组件(如menubutton),而不应该描述他的状态和外观(如redbig
    <!-- Correct. `menu` block 是有语义的 -->
    <div class="menu"></div>
    
    <!-- Incorrect. red-text描述的是外观 -->
    <div class="red-text"></div>
    
  • block不应该影响它的外界环境:你不能给block设置margin或改变它的定位(position)
  • 你应该使用class(如.menu)来表示block,而不能使用标签(如div)或者id(如#myButton
  • block可以有任意层级的相互嵌套,例如,一个头部block可以包括一个logo block,一个搜索表单block,和一个用户认证block:
<!-- `header` block -->
<header class="header">
    <!-- Nested `logo` block -->
    <div class="logo"></div>

    <!-- Nested `search` block -->
	<div class="search"></div>
</header>

在这里插入图片描述

  • 由于block是一个独立的实体,我们可以轻易地改变他们的布局而不用去修改block(如下的logo和用户认证)里面的CSS和Javascript代码:

在这里插入图片描述
在这里插入图片描述

Element

一个元素是块的一部分,具有某种功能。元素是依赖上下文的:它们只有处于他们应该属于的块的上下文中时才是有意义的。
特性:

  • 同样地,element的name应该描述它是一个什么元素(如itemtext),而不应该描述他的状态和外观(如redbig

  • element的全名一般表示为block-name__element-name,element的名字跟block的名字一般用双下划线分隔(__)

    <!-- `search-form` block -->
    
    	<!-- `input` element in the `search-form` block -->
    	<input class="search-form__input">
    
    	<!-- `button` element in the `search-form` block -->
    	<button class="search-form__button">Search</button>
    
  • element可以有任意层级的相互嵌套

  • 一个block下的所有element无论相互层级如何,都要摊开扁平的属于Block
    所以 BEM 最多只有 B+E+M 三级,不可能出现 B+E+E+…+E+M 超长class名,也要求E不能同名:

    <!--
    	Correct. The structure of the full element name follows the pattern:
    	`block-name__element-name`
    -->
    <div class="search-form">
    	<div class="search-form__content">
    		<input class="search-form__input">
    
    		<button class="search-form__button">Search</button>
    	</div>
    </div>
    
    <!--
    	Incorrect. The structure of the full element name doesn't follow the pattern:
    	`block-name__element-name`
    -->
    <div class="search-form">
    	<div class="search-form__content">
    		<!-- Recommended: `search-form__input` or `search-form__content-input` -->
    		<input class="search-form__content__input">
    
    		<!-- Recommended: `search-form__button` or `search-form__content-button` -->
    		<button class="search-form__content__button">Search</button>
    	</div>
    </div>
    

虽然一个block可以有任意层级的element:

<div class="block">
    <div class="block__elem1">
        <div class="block__elem2">
            <div class="block__elem3"></div>
        </div>
    </div>
</div>

但是在css中,应该将他们写成平级的形式:

	 /* Correct */
	.block {}
	.block__elem1 {}
	.block__elem2 {}
	.block__elem3 {}

	/* Incorrect */
	.block .block__elem1 {}
	.block .block__elem1 .block__elem2 {}

这里,block相当于一个命名空间,保证了element是属于block的

  • 需要注意的是,element是block的可选部件,不是说所有的block都有element的:
<!-- `search-form` block -->
<div class="search-form">
    <!-- `input` block -->
    <input class="input">

    <!-- `button` block -->
    <button class="button">Search</button>
</div>
什么时候应该创建block?什么时候应该创建element?

当一段代码能够被复用并且独立于其他页面组件的时候,你就应该创建一个block。当一段代码不能离开父元素(block)单独使用时,你就应该创建一个element。有一个例外,当你的element必须分成更小的subelement时,此时element的层级会比较深,为了简化结构,你可能需要创建一个服务性的block来代替创建一个element

Modifier

Modifier用来定义block或element的样式,状态或者行为
特性:

  • modifier的name应该能够描述block或element的样式状态或者行为,如menu--focusedmenu--disabledel-button--primaryel-button--warning

  • modifier本质上就像HTML的属性一样,同样的block因为有不同的modifier而看起来不一样。如下图,因为使用了不同的modifier,menu的样式也会不同:
    在这里插入图片描述

    elementUI组件库的按钮因为加了不同的modifier而呈现不同的样式:
    在这里插入图片描述

  • modifier不能离开block或element单独使用:

    <!--
    	Correct. The `search-form` block has the `search-form--small` modifier
    -->
    <div class="search-form search-form--small">
    	<input class="search-form__input">
    
    	<button class="search-form__button">Search</button>
    </div>
    
    <!-- Incorrect. The modified class `search-form` is missing -->
    <div class="search-form--small">
    	<input class="search-form__input">
    
    	<button class="search-form__button">Search</button>
    </div>
    
Mix(混合)

混合是一项在一个单一DOM节点上混合使用不同的BEM实体的技术
来看一个例子:

<!-- `header` block -->
<div class="header">
    <!--
        The `search-form` block is mixed with the `search-form` element
        from the `header` block
    -->
    <div class="search-form header__search-form"></div>
</div>

我们知道,我们不应该给block设置margin或改变它的定位(position),这会影响block的外界环境。在这个例子中,我们在header块中联合了search-form块和search-form元素的行为和样式,这就允许我们在header_search-form元素里面设置margin和定位,从而使search-form块保持独立性和通用性。这样,我们可以任意地方使用search-form块而不会影响它的外界环境

开放/封闭原则

关于开放封闭原则,其核心的思想是软件实体应该是可扩展,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。将这个原则应用到BEM就是,我们应该使用modifier去拓展block或element的样式,而不应该去修改block或element的基础样式,来看一个例子:

<button class="button">...</button>
<button class="button">...</button>

.button {
    font-family: Arial, sans-serif;
    text-align: center;
    font-size: 18px;
    line-height: 18px;
}

当我们需要改变button的size时,我们需要使用modifier,而不是直接去修改.button的样式:

<button class="button">...</button>
<button class="button button--small">...</button>

.button {
    font-family: Arial, sans-serif;
    text-align: center;
	/* 不能直接修改这里 */
    font-size: 18px;
    line-height: 18px;
}

.button--small {
    font-size: 13px;
    line-height: 13px;
}
一些例子

elementUI组件库就是用BEM来组织css代码的,来看一下它的部分源码:

// mixins.scss

$namespace: 'el';
$element-separator: '__';
$modifier-separator: '--';
$state-prefix: 'is-';

/* BEM
 -------------------------- */
@mixin b($block) {
  $B: $namespace+'-'+$block !global;

  .#{$B} {
    @content;
  }
}

@mixin e($element) {
  $E: $element !global;
  $selector: &;
  $currentSelector: "";
  @each $unit in $element {
    $currentSelector: #{$currentSelector + "." + $B + $element-separator + $unit + ","};
  }

  // 如果$selector里面含有一些特殊嵌套规则,比如含有modifier或伪类
  @if hitAllSpecialNestRule($selector) {
    @at-root {
      #{$selector} {
        #{$currentSelector} {
          @content;
        }
      }
    }
  } @else {
    @at-root {
      #{$currentSelector} {
        @content;
      }
    }
  }
}

@mixin m($modifier) {
  $selector: &;
  $currentSelector: "";
  @each $unit in $modifier {
    $currentSelector: #{$currentSelector + & + $modifier-separator + $unit + ","};
  }

  @at-root {
    #{$currentSelector} {
      @content;
    }
  }
}

@mixin when($state) {
  @at-root {
    &.#{$state-prefix + $state} {
      @content;
    }
  }
}

elementUI中一个DOM结构比较简单的badge组件:

<template>
  <div class="el-badge">
    <slot></slot>
    <transition name="el-zoom-in-center">
      <sup
        v-show="!hidden && (content || content === 0 || isDot)"
        v-text="content"
        class="el-badge__content"
        :class="[
          'el-badge__content--' + type,
          {
            'is-fixed': $slots.default,
            'is-dot': isDot
          }
        ]">
      </sup>
    </transition>
  </div>
</template>
// badge.scss

// .el-badge
@include b(badge) {
  position: relative;
  vertical-align: middle;
  display: inline-block;

  // .el-badge__content
  @include e(content) {
    background-color: $--badge-fill;
    border-radius: $--badge-radius;
    color: $--color-white;
    display: inline-block;
    font-size: $--badge-font-size;
    height: $--badge-size;
    line-height: $--badge-size;
    padding: 0 $--badge-padding;
    text-align: center;
    white-space: nowrap;
    border: 1px solid $--color-white;

	// .is-fixed
    @include when(fixed) {
      position: absolute;
      top: 0;
      right: #{1 + $--badge-size / 2};
      transform: translateY(-50%) translateX(100%);

      @include when(dot) {
        right: 5px;
      }
    }

	// .is-dot
    @include when(dot) {
      height: 8px;
      width: 8px;
      padding: 0;
      right: 0;
      border-radius: 50%;
    }

    @each $type in (primary, success, warning, info, danger) {
	  .el-badge--primary .el-badge--success .el-badge--warning .el-badge--info .el-badge--danger
      @include m($type) {
        @if $type == primary {
          background-color: $--color-primary;
        } @else if $type == success {
          background-color: $--color-success;
        } @else if $type == warning {
          background-color: $--color-warning;
        } @else if $type == info {
          background-color: $--color-info;
        } @else {
          background-color: $--color-danger;
        }
      }
    }
  }
}

在element中还引入了state这个概念(如上面代码中的is-fixed, is-dot),相当于把在modifier中的状态迁移到state中。
大家有兴趣可以去看一下element的源码,可以了解到如何将BEM应用到实际的大型应用中,加深对BEM的理解

当项目中使用的是Less时,Less没有@at-root这个特性,那么要如何实现BEM呢?一种方法是使用Parent selectors

.block {
  color: red;

  &__element {
    color: blue;
  }

  &--modifier {
    color: orange;
  }
}

编译出来的结果:

.block {
  color: red;
}

.block__element {
  color: blue;
}

.block--modifier {
  color: orange;
}
总结:

如今挺多前端项目都没有一个统一的规范,前端开发人员都喜欢按照自己的套路去写代码,导致同一个项目当中有不同的命名风格,缩进风格等。虽然每个开发人员用自己舒适的套路去开发很爽,但当要去改别人的代码时,就会非常头疼了;当项目迭代到一定规模之后,就会变得很难维护了。所以一个团队有统一的规范是很重要的,本文介绍的BEM规范虽然也有缺陷(比如命名繁琐,增加了文件体积,另外编写样式,写选择器麻烦,从而增加了工作量),但你很难找到比它更好的命名规范了,从一种思想上来看,BEM还是有很多值得大家去思考的,特别是用于维护项目,或者跨团队的开发当中,它的好处会明显见长。大家不妨去尝试一下,将其运用到项目当中去。

只有适合自己团队的规范才是好规范,你可能不喜欢BEM,你也可以去选择一些其他的命名规范(比如NEC)。团队最重要的是统一,大家统一用一种套路,一种规范,这样上下游合作,内部合作都会极大地降低沟通成本。

由于本人水平有限,如果文中有错误的地方欢迎大家指点勘误,谢谢!

参考资料:
BEM
理解 CSS 命名规范 --BEM
编写模块化CSS:BEM
决战BEM, 10个常见问题及其解决方案
如何看待 CSS 中 BEM 的命名方式

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值