这段时间在整理前端部分的代码规范,前面提到的CSS规范里面会涉及到选择器的命名,就参考BEM的命名规范,内容整理以下,供你们参考,请斧正!如你们有兴趣,可移步至CSS编码规范css
BEM是由Yandex公司推出的一套CSS命名规范,官方是这么描述它的:html
BEM是一种让你能够快速开发网站并对此进行多年维护的技术。前端
一开始,Yandex公司推出的BEM,包括了规范以及其配套构建工具。现在提到的BEM主要是指其中的规范,在BEM最新的推广页中,对其的描述为:segmentfault
BEM是一种命名方法,可以帮助你在前端开发中实现可复用的组件和代码共享。框架
考虑如下场景:
场景一:开发一个弹窗组件,在现有页面中测试都没问题,一段时间后,新需求新页面,该页面一打开这个弹窗组件,页面中样式都变样了,一查问题,原来是弹窗组件和该页面的样式相互覆盖了,接下来就是修改覆盖样式的选择器...又一段时间,又开发新页面,每次为元素命名都心惊胆战,求神拜佛,没写一条样式,F5都按多几回,每一个组件都测试一遍...编辑器
场景二:承接上文,因为页面和弹窗样式冲突了,因此把页面的冲突样式的选择器加上一些结构逻辑,好比子选择器、标签选择器,借此让选择器独一无二。一段时间后,新同事接手跟进需求,对样式进行修改,因为选择器是一连串的结构逻辑,看不过来,嫌麻烦,就干脆在样式文件最后用另外一套选择器,加上了覆盖样式...接下来又有新的需求...最后的结果,一个元素对应多套样式,遍及整个样式文件...工具
以往开发组件,咱们都用“重名几率小”或者干脆起个“当时认为是独一无二的名字”来保证样式不冲突,这是不可靠的。
理想的状态下,咱们开发一套组件的过程当中,咱们应该能够随意的为其中元素进行命名,而没必要担忧它是否与组件之外的样式发生冲突。测试
BEM解决这一问题的思路在于,因为项目开发中,每一个组件都是惟一无二的,其名字也是独一无二的,组件内部元素的名字都加上组件名,并用元素的名字做为选择器,天然组件内的样式就不会与组件外的样式冲突了。网站
这是经过组件名的惟一性来保证选择器的惟一性,从而保证样式不会污染到组件外。编码
BEM的意思就是块(block)、元素(element)、修饰符(modifier),是由Yandex团队提出的一种前端命名方法论。这种巧妙的命名方法让你的CSS类对其余开发者来讲更加透明并且更有意义。BEM命名约定更加严格,并且包含更多的信息,它们用于一个团队开发一个耗时的大项目。
命名约定的模式以下:
.block {} .block__element{} .block--modifier {}
.block
表明了更高级别的抽象或组件。
.block__element
表明.block的后代,用于造成一个完整的.block的总体。
.block--modifier
表明.block的不一样状态或不一样版本。
BEM的关键是光凭名字就能够告诉其余开发者某个标记是用来干什么的。 经过浏览HTML代码中的class属性,你就可以明白模块之间是如何关联的:有一些仅仅是组件,有一些则是这些组件的子孙或者是元素,还有一些是组件的其余形态或者是修饰符。
咱们用一个类比/模型来思考一下下面的这些元素是怎么关联的:
.person {} .person__hand {} .person--female {} .person--female__hand {} .person__hand--left {}
顶级块是‘person’,它拥有一些元素,如‘hand’。一我的也会有其余形态,好比女性,这种形态进而也会拥有它本身的元素。下面咱们把他们写成‘常规’CSS:
.person {} .hand {} .female {} .female-hand {} .left-hand {}
这些‘常规’CSS都是有意义的,可是它们之间却有些脱节。就拿.female来讲,是指女性人类仍是某种雌性的动物?还有.hand,是在说一只钟表的指针(译注:英文中hand有指针的意思)?仍是一只正在玩纸牌的手?使用BEM咱们能够得到更多的描述和更加清晰的结构,单单经过咱们代码中的命名就能知道元素之间的关联。BEM真是强大。
再来看一个以前用‘常规’方式命名的.site-search的例子:
<form class="site-search full"> <input type="text" class="field"> <input type="Submit" value ="Search" class="button"> </form>
这些CSS类名真是太不精确了,并不能告诉咱们足够的信息。尽管咱们能够用它们来完成工做,但它们确实很是含糊不清。用BEM记号法就会是下面这个样子:
<form class="site-search site-search--full"> <input type="text" class="site-search__field"> <input type="Submit" value ="Search" class="site-search__button"> </form>
从这种CSS的写法上咱们就已经知道.media__img
和.media__body
必定是位于.media
内部的,并且.media__img--rev
是.media__img
的另外一种形态。仅仅经过CSS选择器的名字咱们就能获取到以上所有信息。
BEM的另一个好处是针对下面这种状况:
<div class="media"> <img src="logo.png" alt="Foo Corp logo" class="img-rev"> <div class="body"> <h3 class="alpha">Welcome to Foo Corp</h3> <p class="lede">Foo Corp is the best, seriously!</p> </div> </div>
光从上面的代码来看,咱们根本不明白.media和.alpha两个class彼此之间是如何相互关联的?一样咱们也无从知晓.body和.lede之间,或者.img-rev 和.media之间各是什么关系?从这段HTML(除非你对那个media对象很是了解)中咱们也不知道这个组件是由什么组成的和它还有什么其余的形态。若是咱们用BEM方式重写这段代码:
<div class="media"> <img src="logo.png" alt="Foo Corp logo" class="media__img--rev"> <div class="media__body"> <h3 class="alpha">Welcome to Foo Corp</h3> <p class="lede">Foo Corp is the best, seriously!</p> </div> </div>
咱们立马就能明白.media是一个块,.media__img--rev
是一个加了修饰符的.media__img
的变体,它是属于.media
的元素。而.media__body
是一个还没有被改变过的也是属于.media
的元素。全部以上这些信息都经过它们的class名称就能明白,由此看来BEM确实很是实用。
一般人们会认为BEM这种写法难看。我敢说,若是你仅仅是由于这种代码看上去不怎么好看而羞于使用它.
那么你将错失最重要的东西。除非使用BEM让代码增长了没必要要的维护困难,或者这么作确实让代码更难读了,那么你在使用它以前就要三思而行了。可是,若是只是“看起来有点怪”而事实上是一种有效的手段,那么咱们在开发以前固然应该充分考虑它。是,BEM看上去确实怪怪的,可是它的好处远远超过它外观上的那点瑕疵。
BEM可能看上去有点滑稽,并且有可能致使咱们输入更长的文本(大部分编辑器都有自动补全功能,并且gzip压缩将会让咱们消除对文件体积的担心),可是它依旧强大。
BEM的命名中包含了模块名,长长的命名会让HTML标签会显得臃肿。
其实每一个使用BEM的开发团队多多少少会改变其命名规范,好比Instagram团队使用的驼峰式:
.blockName-elementName--modifierName { /* ... */ }
还有单下划线:
.block-name_element-name--modifierName { /* ... */ }
还有修饰器名用单横线链接:
.blockName__elementName-modifierName { /* ... */ }
其实这些对缩短命名没有多大的帮助,但咱们也无需担忧文件体积的问题,因为服务端有gzip压缩,BEM命名相同的部分多,压缩下来的体积不会太大。另外如今都用IDE来编写代码了,有自动提示功能,也无须担忧重复的输入过长的名字。由于命名长,咱们是否是能够用子代选择器来代替BEM命名?这样至少在HTML编写时,让HTML标签看起来美观一点。
当你真正使用BEM的时候,重要的是,请记住你不必真的在每一个地方都用上它。好比:
.caps { text-transform: uppercase; }
这条CSS不属于任何一个BEM范畴,它仅仅只是一条单独的样式。
另外一个没有使用BEM的例子是:
.site-logo {}
这是一个logo,咱们能够把它写成BEM格式,像下面这样:
.header {} .header__logo {}
但咱们不必这么作。使用BEM的诀窍是,你要知道何时哪些东西是应该写成BEM格式的。由于某些东西确实是位于一个块的内部,但这并不意味它就是BEM中所说的元素。这个例子中,网站logo彻底是恰巧在.header的内部,它也有可能在侧边栏或是页脚里面。一个元素的范围可能开始于任何上下文,所以你要肯定只在你须要用到BEM的地方你才使用它。
再看一个例子:
<div class="content"> <h1 class="content__headline">Lorem ipsum dolor...</h1> </div>
在这个例子里,咱们也许仅仅只须要另外一个class,能够叫它.headline;它的样式取决于它是如何被层叠的,由于它在.content的内部;或者它只是恰巧在.content的内部。若是它是后者(即恰巧在.content的内部,而不老是在)咱们就不须要使用BEM。
然而,一切都有可能潜在地用到BEM。咱们再来看一下.site-logo的例子,想象一下咱们想要给网站增长一点圣诞节的气氛,因此咱们想有一个圣诞版的logo。因而咱们有了下面的代码:
.site-logo {} .site-logo--xmas {}
咱们能够经过使用--修饰符来快速地为咱们的代码构建另外一个版本。
BEM最难的部分之一是明确做用域是从哪开始和到哪结束的,以及何时使用(不使用)它。随着接触的多了,有了经验积累,你慢慢就会知道怎么用,这些问题也再也不是问题。
一开始了解BEM的时候,可能会产生误解,出现如下不正确的命名方式:
<div class="page-btn"> <!-- ... --> <ul class="page-btn__list"> <li class="page-btn__list__item"> <a href="#" class="page-btn__list__item__link">第一页</a> </li> </ul> <!-- ... --> </div>
分页组件有个ul列表名为:page-btn__list
,列表里面存放每一页的按钮,名为:page-btn__list__item__link
,这是不对的。
首先,有悖BEM命名规范,BEM的命名中只包含三个部分,元素名只占其中一部分,因此不能出现多个元素名的状况,因此上述每一页的按钮名能够改为:page-btn__btn。
而应该以下:
<div class="page-btn"> <!-- ... --> <ul class="page-btn__list"> <li class="page-btn__item"> <a href="#" class="page-btn__btn">第一页</a> </li> </ul> <!-- ... --> </div>
其次,有悖BEM思想,BEM是不考虑结构的,好比上面的分页按钮,即便它是在ul列表里面,它的命名也不该该考虑其父级元素。当咱们遵循了这个规定,不管父元素名发生改变,或是模块构造发生的改变,仍是元素之间层级关系互相变更,这些都不会影响元素的名字。
因此即便需求变更了,分页组件该有按钮仍是要有按钮的,DOM构造发生变更,至多也就不一样元素的增删减,模块内名称也随之增删减,而不会出现修更名字的状况,也就不会由于名字变更,牵涉到JS文件的修改,或样式文件的修改。
BEM修饰器表明着元素的状态,但有时候元素的状态须要js来控制,此时遵循规范没有任何好处,好比激活状态,BEM推荐的写法是:
.block__element { display: none; } .block__element--active { display: block;
当用js为该元素添加状态时,咱们须要知道该元素的名字block__element
,这样咱们才能推导出它的激活状态为block__element--active
,这是不合理的,由于不少时候咱们没法得知元素的名称,因此这时候,咱们应该统一js控制状态的类名格式,好比is-active
、js-active
等等,这些类名只用做标识,不予许有默认的公共样式:
.block__element { display: none; } .block__element.is-active { display: block; }
BEM能够不须要用到原子类,可是若是已经引入了相似Bootstrap的框架,也不必强制避免使用原子类,好比pull-right
、ellipsis
、clearfix
等等类,这些类很是实用,和BEM是能够互补的。
在组件开发中其实不推荐使用原子类,由于这会下降组件的可复用性。可复用性的最理想状态就是组件不只仅在不一样的页面中表现一致,在跨项目的状况下,也可以运行良好。若是组件的样式由于依赖于某几个原子类就要依赖整个Bootstrap库,那么组件d 迁移负担就重不少了。
原子类更适合应用在实际页面中,这是由于页面变更大并且不可复用,假设在header中,咱们用到了两个组件logo和user-panel(用户操做面板),两个组件分别置于header的左侧和右侧,咱们能够这么写:
<div class="header clearfix"> <div class="logo pull-left"><!-- ... --></div> <div class="user-panel pull-left"><!-- ... --></div> </div>
header能够封装成一个模块,但它复用程度不高,不能算是组件,因此即便使用原子类也没有关系。在项目中,使用原子类以前应该考虑一下,这个场景是否变更大并且不可复用,若是是的话,咱们能够放心的使用原子类。
组件应该是“自洽的”,其自己就应该构成了一个“生态圈”,也就是说,他几乎不须要外部供给,自给自足就可以运转下去。
子代选择器的方式是,经过组件的根节点的名称来选取子代元素。按照这个思路,分页按钮样式能够这么写:
<div class="page-btn"> <!-- ... --> <ul class="list"></ul> <!-- ... --> </div>
.page-btn { /* ... */ } .page-btn .list { /* ... */ }
HTML看起来美观多了,但这解决了样式冲突问题么?试想下,若是让你来接手这个项目,要增长一个需求,新增一个组件,你命名放心么?
你面临的问题是:你打开组件目录,里面有个分页组件,叫作page-btn,但是你彻底不知道要怎么给新组件命名,由于即便新组件模块名与page-btn不同,也不能保证新组件与分页组件不冲突。
好比新的需求是“新增一个列表组件”,若是该组件的名字叫作list,其根节点的名字叫list,那么这个组件下面写的样式,就极可能和.page-btn .list的样式冲突:
.list { /* ... */ }
这还仅仅只有两个组件而已,实际项目中,十几个或几十个组件,难道咱们要每一个组件都检查一下来“新组件名是否和以往组件的子元素命名冲突了”么?这不现实。
BEM禁止使用子代选择器,以上是缘由之一。子代选择器很差的地方还在于,若是层次关系过长,逻辑不清晰,很是不利于维护。为了懒得命名或者追求所谓的“精简代码”,写出下面这种选择器:
.page-btn button:first-child {} .page-btn ul li a {} /* ... */ /* 维护代码,新增需求 */ .page-btn .prev {}
用DOM结构层次关系结构来定位元素,可能会由于需求改变而大面积的重写样式文件。试想一下维护这类代码有多么痛苦,咱们要一边检查该元素的上下文DOM结构,一边对照着css文件,一一对比,找到该元素对应的样式,也就是说我为了改一个元素的代码,须要不断翻阅HTML文件和CSS文件,可维护性很是之差。更有甚者,来维护这块代码的同事,直接在样式文件最后添加覆盖样式,这会形成一个很是严重的问题了:同一个元素样式零散的分布在文件的不一样地方,并且定位该元素的选择器也可能各不相同。
这样的样式文件只会越写越糟糕,能够说,当咱们用子代选择器来定位元素时,这个样式文件就已经注定是要被翻来覆去的重构的了,甚至,每一个来维护这个文件的人都会将其重构一遍。
子代选择器还会形成权重过大的问题,当咱们要作响应式的时候,某个带样式的元素须要适配不一样的屏幕,此时,咱们还要不断的确认该元素以前的选择器写法!为了覆盖前面权重过大的样式,甚至经过添加额外的类名或标签名来增长权重。可想而知,此后这个样式文件的维护难度就像雪球同样,越滚越大。
若是咱们用的是BEM,要覆盖样式很简单:找到要覆盖样式的元素,得知它的类名,在媒体查询中,用它的类名做为选择器,写下覆盖样式,样式就覆盖成功了,不须要担忧前面样式的权重过大。