CSS是一门几十分钟就能入门,可是却须要很长的时间才能掌握好的语言。它有着它自身的一些复杂性与局限性。其中很是重要的一点就是,自己不具有真正的模块化能力。css
系列文章连接 ↓ ↓html
CSS中虽然有@import
功能。然而,咱们都知道,这里的@import
仅仅是表示引入相应的CSS文件,但其模块化核心问题并未解决——CSS文件中的任何一个选择器都会做用在整个文档范围里。前端
所以,其实咱们面临的最大问题就是——全部的选择器都是在一个全局做用域内的。一旦引入一个新的CSS文件,就有着与预期不符的样式表现的风险(由于一些不可预测的选择器)。java
而现在的前端项目规模愈来愈大,已经不是过去随便几个css、js文件就能够搞定的时代。与此同时的,对于一个大型的应用,前端开发团队每每也再也不是一两我的。随着项目与团队规模的扩大,甚至是项目过程当中人员的变更,如何更好进行代码开发的管理已经成为了一个重要问题。react
回想一下,有多少次:webpack
用CSS实现一些样式每每并非最困难的所在,难的是使用一套合理的CSS架构来支持团队的合做与后续的维护。git
What we want is to be able to write code that is as transparent and self-documenting as possible.github
本系列文章会介绍一些业界在探索CSS模块化进程中提出的方案。本篇主要会讲解BEM方法论,并将其与CSS命名空间结合。web
BEM实际上是一种命名的规范。或者说是一种class书写方式的方法论(methodology)。BEM的意思就是块(block)、元素(element)、修饰符(modifier),是由Yandex团队提出的一种前端命名方法论。在具体CSS类选择器上的表现就像下面这样shell
.block {}
.block__element {}
.block--modifier {}
.block__element--modifier {}
复制代码
其中,block表示的是独立的分块或组件;element表示每一个block中更细粒度的元素;modifier则一般会用来表示该block或者element不一样的类型和状态。
举个例子,例如咱们有一个列表
<ul class="list">
<li class="item">learn html</li>
<li class="item underline">learn css</li>
<li class="item">learn js</li>
</ul>
复制代码
列表容器的class为.list
,列表内每条记录的class为.item
,其中,还为第二个条记录添加了一个下划线.underline
。简单的css以下
.list {
margin: 15px;
padding: 0;
}
.list .item {
margin: 10px 0;
border-left: 3px solid #333;
font-size: 15px;
color: #333;
list-style: none;
}
.list .underline {
color: #111;
text-decoration: underline;
}
复制代码
这样的命名方式,咱们在阅读html时并不能迅速了解:.item
是只能在.list
中使用么,它是仅仅定义在这个组件内的一部分么?.underline
是一个通用样式么,我想修改列表的中underline的记录为红色,这会影响到项目其余地方么?
这时候,咱们就可使用BEM方式来命名咱们的class
.list {
margin: 15px;
padding: 0;
}
.list__item {
margin: 10px 0;
border-left: 3px solid #333;
font-size: 15px;
color: #333;
list-style: none;
}
.list__item--underline {
color: #111;
text-decoration: underline;
}
复制代码
<ul class="list">
<li class="list__item">learn html</li>
<li class="list__item list__item--underline">learn css</li>
<li class="list__item">learn js</li>
</ul>
复制代码
这段代码的一大优点就是增长了它的自解释性:必定程度上,它的class名自己就是一个简易的文档。
这里还须要避免一个误区,BEM命名规范里,咱们的CSS并不会关心HTML中dom元素的层级结构。它的核心着眼点仍是咱们定义的块(block)、元素(element)、修饰符(modifier)这三部分。由于关注点不一样,因此一个block内的全部element,在CSS中并不会考虑层级,所以也就没有.list__item__avatar
这种写法
<ul class="list">
<li class="list__item">

learn html
</li>
<li class="list__item list__item--underline">learn css</li>
<li class="list__item">learn js</li>
</ul>
复制代码
而是把这个img
也看做block中的元素.list__avatar
<ul class="list">
<li class="list__item">

learn html
</li>
<li class="list__item list__item--underline">learn css</li>
<li class="list__item">learn js</li>
</ul>
复制代码
从这个例子看一看出,CSS部分并不关心dom层级结构,而是在block下面有哪些element,这些element又有哪些modifier。
基于这个思想,咱们能够知道,若是一个block里面含有其余block并不会违反BEM的原则。例如上面这个列表的例子,其中头像avatar本来只是一个简单的element,如今若是变成了一个很复杂的组件——包括图片、姓名和标签,那么可能会有这么一个block
<ul class="list">
<li class="list__item">
<div class="list__avatar">
<img class="list__head list__head--female" />
<span class="list__name"></span>
<span class="list__tag"></span>
</div>
learn html
</li>
<li class="list__item list__item--underline">learn css</li>
<li class="list__item">learn js</li>
</ul>
复制代码
咱们能够为avatar建立一个新的block
<ul class="list">
<li class="list__item">
<div class="avatar">
<img class="avatar__head avatar__head--female" />
<span class="avatar__name"></span>
<span class="avatar__tag"></span>
</div>
learn html
</li>
<li class="list__item list__item--underline">learn css</li>
<li class="list__item">learn js</li>
</ul>
复制代码
那么你可能会有疑问,何时须要在将一个elment从新抽象为新的block呢?仅仅当咱们的dom元素变得不少的时候么?
其实,BEM中的block必定程度上能够理解为一个“独立的块”。独立就意味着,把这一部分放到其余部分也能够正常展现与使用,它不会依赖其父元素或兄弟元素。而在另外一个维度上面来讲,也就是视觉设计的维度,当UI设计师给出UI稿后,其中的一些设计元素或组件会重复出现,这些部分也是能够考虑的。因此理解UI设计稿并非指简单的还原,其中的设计原则与规范也值得揣摩。
从上面的简单介绍能够看出,BEM有着一些优势
.list__item
和.list__item--underline
都是依赖于.list
的,所以它们不能脱离于.list
存在)固然,BEM仅仅是一种命名规范或建议。在没有约束的状况下,你随时均可以违反。因此咱们能够借助相似BEM-constructor的工具,既帮咱们进行必定的约束,同时也省去一些繁琐的重复工做。在介绍BEM-constructor以前,咱们还须要简单了解一下BEM-constructor中命名空间(namespaces)的基本概念。
命名空间(namespaces)也是一种关于CSS中class命名方式的规范。《More Transparent UI Code with Namespaces》提供了一种命名空间的规范。在BEM的基础上,创建命名空间主要是为了进一步帮助咱们:
命名空间分为如下几种。
当你使用面向对象的CSS(Object-Oriented CSS)时,o-
这个namespace将会很是有用。
o-
时请慎重考虑。c-
应该是一个更为常见的namespace,表示Components(组件)。
.c-list {}
.c-avatar {}
复制代码
从命名中咱们就能知道:这是一个list组件;或者这是一个avatar组件。
c-
表明一个具体的组件。Utilities符合单一职责原则,实现一个具体的功能或效果。其概念有些相似JavaScript中的通用工具方法。例如一个清除浮动的Utility,或者一个文字居中的Utility。
.u-clearfix {}
.u-textCenter {}
复制代码
因为Utilities做为一组工具集,在样式上具备更强的“话语权”,因此!important
在Utilities中会更为常见。当咱们看到下面这段HTML,咱们会更加确信,这个大号的字体是.u-largeFont
这个样式引发的。
<h1 class="title u-largeFont">namespace</h1>
复制代码
u-
前缀,只用在一些通用的工具方法上.当咱们使用Stateful Themes这种定义主题的方式时(后续有机会会介绍一些“自定义主题”的方式),每每咱们会在最外层容器元素中加入一个表明不一样主题的class。这里就会用到t-
。
t-
是一个高层级的命名空间。s-
可能不是这么好理解,由于CSS中并无Scope这个概念(或者说只有一个全局的Scope)。而s-
正是但愿经过命名的方式来创建一个新的Scope。
可是请勿滥用它,只有在你确实须要建立一个新的“做用域”的时候再使用它。例如一个简单场景:CMS。若是你接触过CMS你就会知道,它必定有一个生成或编辑内容的功能。而一般的,咱们会将这部分编辑的内容输出到页面中,并在外部赋予一个新的Scope,用以隔离该部分与外部整个站点的样式。
<nav class="c-nav-primary">
...
</nav>
<section class="s-cms-content">
<h1>...</h1>
<p>...</p>
<ul>
...
</ul>
<p>...</p>
</section>
<ul class="c-share-links">
...
</ul>
复制代码
.s-cms-content {
font: 16px/1.5 serif; /* [1] */
h1, h2, h3, h4, h5, h6 {
font: bold 100%/1.5 sans-serif; /* [2] */
}
a {
text-decoration: underline; /* [3] */
}
}
复制代码
section
部分就是展现CMS中的content(内容)。
慎用,须要万分当心。
BEM-constructor是基于SASS的一个工具。使用BEM-constructor能够帮助规范并快速地建立符合BEM与namespace规范的class。BEM-constructor的语法很是简单。
npm install sass-bem-constructor --save-dev
复制代码
首先在SASS引入@import 'bem-constructor';
,而后使用@include block($name, $type) { ... }
建立block,其中$name
是block的名字,$type
是namespace的类型('object'
, 'component'
和'utility'
)。相似得,使用element($name...)
和modifier($name...)
能够快速生成block中的其余部分。
将最初的例子进行改写
@import 'sass-bem-constructor/dist/_sass-bem-constructor.scss';
@include block('list', 'component') {
margin: 15px;
padding: 0;
@include element('item') {
margin: 10px 0;
border-left: 3px solid #333;
font-size: 15px;
color: #333;
list-style: none;
@include modifier('underline') {
color: #111;
text-decoration: underline;
}
}
}
复制代码
生成的内容以下
.c-list {
margin: 15px;
padding: 0;
}
.c-list__item {
margin: 10px 0;
border-left: 3px solid #333;
font-size: 15px;
color: #333;
list-style: none;
}
.c-list__item--underline {
color: #111;
text-decoration: underline;
}
复制代码
BEM-constructor支持咱们以前提到的各类命名空间。例如theme($themes...)
,scope($name)
等等。语法格式基本相似。
此外,若是不想使用namespace,也能够手动关闭
$bem-use-namespaces: false; // defaults to true
复制代码
同时也支持更改命名空间的前缀名
$bem-block-namespaces: (
'object': 'obj', // defaults to 'o'
'component': 'comp', // defaults to 'c'
'utility': 'helper', // defaults to 'u'
);
复制代码
固然,若是你不喜欢BEM中的__
,--
的链接线,也能够自定义
$bem-element-separator: '-'; // Defaults to '__'
$bem-modifier-separator: '-_-_'; // Defaults to '--'
复制代码
BEM和namespace是一种命名规范,或者说是一种使用建议。他的目的是帮助咱们写出更易维护与协做的代码,更多的是在代码规范的层面上帮助咱们解决CSS模块化中的问题。然而,也不得不认可,它距离咱们梦想中的CSS模块化还有这很长的距离。可是不管如何,其中蕴含的一些组件化与CSS结构组织方式的想法也是值得咱们去思考的。