干货:CSS编写策略之BEM

·  阅读 87

简短的概括:

1、BEM简介

2、为什么使用BEM

3、BEM的命名规则

4、BEM解决的问题

5、基于 SCSS 的 BEM 方法的实现(element-ui源码解读)

BEM导图

一、BEM简介

BEM的意思就是块(block)、元素(element)、修饰符(modifier),是由Yandex团队提出的一种前端命名方法论。这种巧妙的命名方法让你的CSS类对其他开发者来说更加透明而且更有意义。BEM命名约定更加严格,而且包含更多的信息,它们用于一个团队开发一个耗时的大项目。

二、为什么使用BEM

1)更语意化,可读性更强

通过双下划线(__), 双横杠(--) 等符号代码维护者可以轻松理解每一部分的意义,更强的可读性往往意味着更低的维护成本。

2)模块化,减少层叠带来的样式覆盖的问题

Block是完全独立的存在,其内部的element/modifier的样式都在这个block的命名空间下书写的,所以不会收到其他外部样式的影响,不存在样式覆盖的问题。

3)增强样式的重用性

就像js组合不同的组件得到更复杂的组件一样,我们也可以通过组合不同的block得到更复杂的样式,例如使用.b-btn, .b-input来组合一个简单的form样式,从而提高代码的可复用性,从另一方面讲也是降低了维护成本。

4)更容易做项目迁移

因为block样式是相对独立的,如果在其他项目有需要,我们完全可以讲某个单独的block相关的样式应用到其他项目中。

三、BEM的命名规则

命名约定的模式如下:

.block{}

.block__element{}

.block--modifier{}

说明:

block 代表了更高级别的抽象或组件。

block__element 代表.block的后代,用于形成一个完整的.block的整体。

block--modifier 代表.block的不同状态或不同版本。

之所以使用两个连字符和下划线而不是一个,是为了让你自己的块可以用单个连字符来界定,如:

.site-search{} /* 块 */

.site-search__field{} /* 元素 */

.site-search--full{} /* 修饰符 */

* 举个栗子 🌰

1)常规的css

<div class="person">
    <div class="female">
        <p class="color"></p>
    </div>
    <div class="male">
        <p class="color"></p>
    </div>
</div>
复制代码

这些CSS类名真是太不精确了,并不能告诉我们足够的信息。尽管我们可以用它们来完成工作,但它们确实非常含糊不清。

2)BEM命名规范

<div class="person">
    <div class="person_female">
        <p class="person_female--color"></p>
    </div>
    <div class="person_male">
        <p class="person_male--color"></p>
    </div>
</div>
复制代码

四、BEM解决的问题

css的样式应用是全局性的,没有作用域可言。考虑以下场景:

**场景一:**开发一个弹窗组件,在现有页面中测试都没问题,一段时间后,新需求新页面,该页面一打开这个弹窗组件,页面中样式都变样了,一查问题,原来是弹窗组件和该页面的样式相互覆盖了,接下来就是修改覆盖样式的选择器...

**场景二:**承接上文,由于页面和弹窗样式冲突了,所以把页面的冲突样式的选择器加上一些结构逻辑,比如子选择器、标签选择器,借此让选择器独一无二。一段时间后,新同事接手跟进需求,对样式进行修改,由于选择器是一连串的结构逻辑,看不过来,嫌麻烦,就干脆在样式文件最后用另一套选择器,加上了覆盖样式...接下来又有新的需求...最后的结果,一个元素对应多套样式,遍布整个样式文件...

BEM解决问题的思路

由于项目开发中,每个组件都是唯一无二的,其名字也是独一无二的,组件内部元素的名字都加上组件名,并用元素的名字作为选择器,自然组件内的样式就不会与组件外的样式冲突了。

BEM的命名规矩:block-name__element-name--modifier-name,也就是模块名 + 元素名 + 修饰器名。

五、element-ui源码解读 BEM 方法

首先来看一个bem命名示例:

.el-message-box{}
.el-message-box__header{}
.el-message-box__header--active{}
复制代码

如果使用已经封装好的bem方法的话,那么可以写成:

@include b('message-box') {
    @include e('header') {
        @include m('active');
    }
}
复制代码

接下来我们来看一下bem方法是如何实现的。

bem方法解析

element/packages/theme-chalk/src/mixins/config.scss 里面定义了如下几个变量

$namespace: 'el';
$element-separator: '__';
$modifier-separator: '--';
$state-prefix: 'is-';
复制代码

然后我们再找到 element/packages/theme-chalk/src/mixins/mixins.scss 文件,找到b,e,m方法。

/* 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 + ","};
  }
  @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;
    }
  }
}
复制代码

**说明:**代码量不多,逻辑也不复杂,语法有点晦涩难懂,解释如下:

  1. !global 变量提升,将局部变量提升为全局变量,在其他函数体内也能访问到此变量。

  2. @at-root将父级选择器直接暴力的改成根选择器。

  3. #{}插值,可以通过 #{} 插值语法在选择器和属性名中使用 SassScript 变量。

总结:

BEM 最难的部分之一是明确作用域是从哪开始和到哪结束的,以及什么时候使用或不使用它。随着不断使用的经验积累,你慢慢就会知道怎么用,这些问题也不再是问题。技术无好坏,合适方最好。

分类:
前端
标签: