项目开发过程中,一套合适的开发风格指南能够有效提高实际开发速度,降低维护成本。然而在有些项目开发过程中,CSS并没有完善的结构或者遵循命名约定,这导致在项目迭代的过程中,CSS结构变得越来越冗余,既降低了开发效率也影响性能。
现有的CSS编写策略有很多,如OOCSS,SMACSS,SUIT CSS等等,今天我们要介绍的其中一种——BEM。
什么是BEM
BEM是Block,Element,Modifier的首字母缩写。是一种CSS的命名规范。定义了CSS中class的命名规则,如.b-blockname__element_name--modifier-name
。其中以b-
开头的原因稍后会解释。
Block 代表一个完整独立的实体
试想一下js中的组件,我们将一种特定可复用的结构封装成组件,然后我们可以在任何需要的地方使用该组件标签占位,然后就能将一大段复杂结构应用到该地方。
BEM中的block也是类似的原理,Design中的某些设计元素可以被用在多个地方,就可以把这个设计元素的样式提取成一个独立的块,而这个块的名字就是.b-blockname__element-name--modifier-name
中blockname
的部分。对于class命名来说,这个block就相当于命名空间。
举个例子:
//html
<div class="b-popup-modal">
<h1 class="b-popup-modal__title">title</h1>
<div class="b-popup-modal__body">
body content
</div>
<div class="b-popup-modal__footer">
<button class="b-btn b-btn--primary b-btn--disable">Submit</button>
<button class="b-btn b-btn--default">Cancel</button>
</div>
</div>
弹窗可以作为网页设计中一个独立元素,所以我们可以把独属于它的样式单独提出来封装到一个css样式块,这个块的class命名中blockname的部分就是popup
。
Element 代表Block里的局部元素
Element指代存在于某个特定block块内部的元素,它可以是block中任意的某个DOM节点。以上面popup的代码为例,h1
就是一个element,而我们在给它的class命名中以b-popup-modal__title
中的__title
来表示BEM规则中E的部分。
Modifiers标示了同一个Blcok/Element不同状态下的不同样式
Modifier可以概括为一种状态标志,对于同一个Block/Element可能有一种基础样式,但是在不同的状态下会呈现出另一种样式,这种所谓的状态就可以通过命名modifier来区分。以上面代码中的.b-btn--disable
为例,--disable
就是一个modifier,同样的--primary
, --default
也是一种modifier。通过定义这样不同的modifier,我们可以很容易估计某个元素会有什么样式或者处于什么状态。
需要注意的是,所以我们可以单独定义一个block作为class,但element和modifier都是和block绑定的非独立模块,block是他们的命名空间,所以element/modifier一定是和block绑定出现的。
使用BEM编写CSS
在了解了BEM的基本原理后,接下来再具体说一下它的实现方法。上面已经说到BEM实际上是一种命名规范,在css里就是指class的命名规范。具体实现规则团队可以根据自己的需求做些微调,但原理都离不开block,element和modifier三部分。以下提供一种通用规则作为参考:
Block,element和modifier名字可以包含字母,数字,下划线(_)和短横杠(-),其中
-
可以作为单词分隔符代替驼峰式命名方式(不采用驼峰式命名的原因是这里html中大小写不敏感)。Block的命名可以以
b-
开头,因为一个网页中不是所有的元素都需要被定义成block,b-
开头可以区分block和普通css class的区别。例如上面提到的.b-popup-modal
,.b-btn
。用双下划线(__)来指示接下来是一个element名字,例如上面
b-popup-modal__title
中的__title
。同样的还有__body
,__footer
。-
通过双短横杠(--)来指示modifier的名字。
不是每一个Block/Element都需要定义Modifier,这完全取决于实际需求。
modifier的位置既可以在block后面,也可以跟在element后面。例如
.b-btn--disable
,.b-nav__nav-item--visited
。对于跟在block后的modifier,后面也可以再追加element,如
.b-btn--disable__icon
。
为什么使用BEM
说了这么多之后,最核心的问题还是为什么要使用BEM,它有什么好?
-
更语意化,可读性更强
通过双下划线(__), 双横杠(--) 等符号代码维护者可以轻松理解每一部分的意义,更强的可读性往往意味着更低的维护成本。
-
模块化,减少层叠带来的样式覆盖的问题
Block是完全独立的存在,其内部的element/modifier的样式都在这个block的命名空间下书写的,所以不会收到其他外部样式的影响,不存在样式覆盖的问题。
.b-popup-modal { //... } .b-popup-modal__title { //... }
以上面代码为例,无论是block本身或者block内部element/modifier,都是针对单个class写样式,不需要使用复杂的css选择器(
.b-popup-modal > .b-popup-modal__title
),最大程度地避免了权重计算。去除掉继承关系,css权重的影响,以block来分的模块化反而体现得更明显。 -
增强样式的重用性
就像js组合不同的组件得到更复杂的组件一样,我们也可以通过组合不同的block得到更复杂的样式,例如使用
.b-btn
,.b-input
来组合一个简单的form样式,从而提高代码的可复用性,从另一方面讲也是降低了维护成本。 -
更容易做项目迁移
因为block样式是相对独立的,如果在其他项目有需要,我们完全可以讲某个单独的block相关的样式应用到其他项目中。
写在最后
综上所诉BEM的特点和用法,通过遵循这样一种命名规范确实能够帮助项目维护一套更易懂,维护成本相对更低,更容易扩展的CSS代码库。
但是一个项目里不是所有的样式都要做成block。相比于BEM的用法,个人觉得更难的其实是如何精确划分好每一个block以及对各种modifier的准确定义。