最近下决心整理一份对页面元素的组织规则和CSS的命名规则,因为深深感受到如果页面上元素太多,没有规则的命名和组织会让网页的维护性大打折扣。参考了几篇文章并且发散了一下,在这里和大家分享
BEM(Block Element Modifier)
本文同时也发表在另一个独立博客 http://qingbob.com/blog/如何组织Html元素与如何进行CSS命名(上)
如何组织页面上的元素,或者说安排元素之间的关系势必会对css命名产生影响;css命名也是对元素关系的映射。BEM这个方法把元素分为三类,代指 Block
, Element
, Modifier
。
举个例子,通常我们会把页面分为header, body, footer部分,可能header部分里面又包括了logo, search, login模块。我们可以把Block理解为一个已经封装好了的组件,比如一个搜索模块(这里说的模块统统指一系列html元素,而非逻辑上的功能代码),它是相对于同级元素比较独立的(也相对于element元素)。
Element则是block中实现具体功能的部件,比如在搜索模块中最起码需要一个按钮(button),一个输入框(input),它和block的重要区别之一是,它没有block那么独立,一旦离开了上下文环境(比如它所在的block),它失去功能上的意义了。
这就非常灵活了,因为某一个block可以是它父元素或者其他元素的element,比如我们单独看搜索模块可能是一个独立的block,但是搜索模块通常又是放在页面的header中,那么搜索模块此时又成了header的element,而header又是一个更大的block。
接下来,BEM作者给出了CSS的具体命名规则希望是
- 一个block必须有唯一的名字(class),比如
<u lclass="menu">
…
</ul>
- 而element的命名需要包括它所属的block的名字,并且以分隔符分隔开:
<ul class="menu">
<li class="menu__item">…</li>
<li class="menu__item">…</li>
</ul>
当然modifier就更好理解了,比如在一个tab模块中,我们需要突出某个tab,就需要给它添加一个的modifier作用的class,这里的modifier可以使特殊的状态,也可以是特殊的属性。就拿上面那个menu例子来说,我们想增大字体,想标识当前选中的菜单项,就可以添加
<ul class="menu menu_size_big">
<li class="menu__item">Index</li>
<li class="menu__item menu__item_state_current">Product</li>
<li class="menu__item">Contact</li>
</ul>
巧合的是,在写这篇文章的同时Smashing Magazine上同时发布了一篇谈BEM现在与将来的文章The Evolution Of The BEM Methodology。主要对BEM过去的里程碑,每个里程碑所得出的一些方法论做了一些总结。比如说谈到BEM的起源其实是为了解决实际项目中css选择器冗长的问题,比如
.result .albums .album .buy { float: left; padding:0.4em1em01.6em; } .result .albums .info i { font-size:85%; }
有甚者
.b-foot div div div div div { background-position:71%; background-image: url(../i/foot-5.png);
}
.b-foot div div div div div div { background-position:87%; background-image: url(../i/foot-6.png);
}
虽然现在看起来很可笑,但我觉得这却是实际中遇见的问题,必须承认我自己有时也陷入这样的怪圈
这篇文章谈为什么有BEM的来龙去脉更生动一些。有兴趣的同学可以看看。其实BEM是一系列的方法论,甚至还包括文件的命名的文件夹分类规则,XSL templates,甚至整个可供参考的框架。
因为在这里我只是作为一个组织html元素和css命名的其中一个方法,只做了简明的介绍和总结。
SMACSS(Scalable and Modular Architecture for CSS)
这个方法论将css分为5类,分别是
- Base
- Layout
- Module
- State
- Theme(忽略这个先)
下面一一进行介绍
Base:
基本(base)规则即那些只使用元素选择器,后代选择等(从不涉及class或者id)的规则,比如
body, form { margin:0; padding:0; } a:hover { color:#03F; }
你可以理解为定义一些全局的css样式。通常这种工作也可以交给reset.css或者normalize.css来完成
Layout Rules:
这里的布局(Layout)指页面上比较大块的区域,比如header,body, footer。而这里的layout rule也分为两类,一类是通过id定义的,比如
#header, #article, #footer { width:960px; margin:auto;
}
#article { border: solid #CCC; border-width:1px00;
}
还有一类可能是在你用了一些css框架的情况下,比如960.gs
.container_12 .grid_6,.container_16 .grid_8 { width:460px; }
作者建议与layout有关的css规则以l-
开头,比如
.l-fixed#sidebar { width:200px; }
Module Rules:
比如说一些登陆,搜索,文章,这样的元素组合我们就可以称之为module。对于这样一些元素的css命名,作者说就免了前缀,直接用模块名称好了,比如
.login { width:200px; }
作者在这里强调的是,避免使用元素选择器。比如开始我们有这么一段html, 有这么一段样式
<div class="fld"><span>Folder Name</span></div>
/* The Folder Module */.fld > span { padding-left:20px; background: url(icon.png);
}
问题是当我们的项目变得庞大,需要增加一个或者更多span标签时
<div class="fld"><span>Folder Name</span><span>(32 items)</span></div>
这会就傻×了吧。所以最好是给标签添加上有语义的class名称,比如
<div class="fld"><span class="fld-name">Folder Name</span><span class="fld-items">(32 items)</span></div>
还有一种情况,当我们在不同的section中使用了同一个module时,我们可能需要根据module所在的section来重新定义样式,比如
.pod { width:100%; } .pod input[type=text]{ width:50%; } #sidebar .pod input[type=text] { width:100%; }
但这样还是会产生问题,会让css变得没有规则和难以维护,所以作者建议添加一个子模块css(Subclassing Modules),比如这么做
<div class="pod pod-constrained">...</div><div class="pod pod-callout">...</div>
.pod { width:100%; } .pod input[type=text]{ width:50%; } .pod-constrained input[type=text]{ width:100%; } .pod-callout { width:200px; } .pod-callout input[type=text]{ width:180px; }
State Rules
state 与之前的modifier概念类似,这种类型的class只起一些修饰作用。并且作者建议使用is-
开头,比如
<div id="header"class="is-collapsed">
<form>
<div class="msg is-error">
There is an error!
</div>
<label for="searchbox" class="is-hidden">Search</label>
<input type="search"id="searchbox">
</form>
</div>
要注意它和之前sub-module的区别
- state规则给layout或者module用都行
- state规则通常由javascript有关(比如错误,高亮,是否折叠),而sub-module是静态不能随意修改的。只是为了区分模块之间的区别
在这个方法论的文章中,我觉得最有价值的一篇是谈到css的可访问性的。首先作者给出的两条建议是
- css不应该依赖DOM树的结构
- css选择器不宜太深
假如我们有这么一段css
#sidebar div { border:1px solid #333;
}
#sidebar div h3 { margin-top:5px;
}
#sidebar div ul { margin-bottom:5px;
}
这段css中的div可以看做一个组件,是由h3和ul组成的。如果我们想把这个组件又放在footer中怎么办,看下面这段代码怎么样
#sidebar div, #footer div { border:1px solid #333;
}
#sidebar div h3, #footer div h3 { margin-top:5px;
}
#sidebar div ul, #footer div ul { margin-bottom:5px;
}
代码这么冗余的原因就是因为它与dom联系的太紧密,不如把这层依赖关系去掉,给它加上一个独立的class
.pod { border:1px solid #333; } .pod > h3 { margin-top:5px; } .pod > ul { margin-bottom:5px; }
我们试图在可维护性,性能,和可读性三者之间保持平衡,虽然css选择器具有一定深度意味着更少的class,但是它增加了可维护性和可读性。除非你压根就不想使用class。
下一期最后看看高手Nicolas的方法论。并且结合淘宝和人人的css规则看看实际的一些应用情况。