BEM 命名给 CSS 以及 html 提供清晰结构,命名空间提供更多信息,模块化提高代码的重用,以达到 CSS 命名语义化、可重用性高、后期维护容易、加载渲染快的要求。
首先,这是一篇仅对 CSS 命名的总结,更多 CSS 规范请参考规范文档。其次 CSS 命名并无对错之分,只是不同的命名方式和代码的优雅程度与后期维护有着密切关系。
@iamdevloper pic.twitter.com/4SRgVDDaiC
— Saeed Prez (@SaeedPrez) 2016年7月14日
CSS 很容易写,但是维护它却很难,因为 CSS 声明属性的性质不具备编程语言流程控制的特点,CSS(Cascading Style Sheets)也可以层叠生效,不同地方的 classes 相互影响。CSS 命名是规范中最重要的部分,它几乎就是 CSS 规范的全部,我们也就是依赖它完成对 CSS 的控制。
曾经我们这样定义选择器:
.fl
float: left
.fr
float: right
.mr10
margin-right: 10px
.pb10
padding-bottom: 10px
.hidden
overflow: hidden
.highlight
color: $green
在 html 中添加选择器:
.fl.pb10.highlight
这就是规则分离的做法,将一些确定值的 CSS 规则单独定义选择器,这样做的目的是提高代码的重用,但是也有很多缺点:
既然还是需要添加语义化的 class 来定义一部分样式,那么分离一部分出去也没有意义,操作上缺点远远大于优点。吸取这种命名的优点,现在依然会使用它,但是只保留了少数语义化且常用的,例如:.hidden
、.highlight
。
html:
.container
.actions
.card_wrapper
.card
.title.light
.content
.list
i.fa.fa-cog
CSS:
body.events,
body.aaa,
body.bbb
.actions
.card_wrapper
.card
.title
.content
.list
.fa
.another
以上是我们现在常见的写法,但是是需要绝对避免的,虽然 class 有一定语义了,但是并不能真正的重用。class 命名太通用了,我们不知道还有什么地方用到 .title
.content
这样的命名,就不敢直接使用,一方面层层嵌套防止冲突,一方面想要重用时就在顶层添加 body.aaa
这样的东西,导致很多样式用不到,写的乱七八糟。
这样一直写下去,旧的 CSS 我们永远不会删,只敷衍写出效果,如此往复循环,最终就会像上面的窗帘,让人忍无可忍。每个页面都有 .content
甚至每个模块都有 .content
,.content
就毫无意义(.content 不是抽象化的全局共用模块)。深层次的嵌套也会降低渲染效率。
另一种常见的:
html:
.main-news-box
h2.news-title
ul.main-news-list
li
a
span
span
CSS:
.main-news-box
.main-news-box .news-title
.main-news-list
.main-news-list li
.main-news-list a
.main-news-list span
这种写法的问题:
.news-title
在别的地方有没有定义?能不能单独使用?其他地方定义的 .news-title
会相互影响,父级更深层次的 li
、a
也会覆盖现有的定义。.main-news-list a
,先渲染页面所有的 a,再渲染 .main-news-list。BEM 是一种真正消除不确定性的命名方式,它使得结构样式更加清晰,我们有足够的信心做任何修改。
-
连接__
与 block 连接--
与 block 连接例如:
.user-home-nav
.user-home-nav-item.user-home-nav-item--small
.user-home-nav-item__icon
.user-home-nav-item__text
CSS 中这样写:
.user-home-nav
&-item
&--small
&__icon
&__text
这样命名的好处:
.user-home-nav
.user-home-nav-item
.user-home-nav-item__icon
.user-home-nav-item__text
.user-home-nav-item--small
缺点?
__
又是 --
,什么乱七八糟的符号,我并不觉得有任何问题,这种命名让结构十分清晰,只看 html 或 CSS 就能看出它对应的模块或者模块的元素或者不同类型的模块,后期维护的增删也十分确定。但是 BEM 最大的问题却是这个:如何给 modifier 下的 element 定义规则呢?
.user-home-nav
&-item
&--small
.user-home-nav__icon
.user-home-nav__text
&__icon
&__text
这样做太不优雅了,太违背 sass 的优雅性质了。或许是我没有找到更好的写法?
还能产生更多的写法,例如 .user-home-nav-item--small__icon
、.user-home-nav-item__icon--small
,但是或多或少会存在问题,还是尽量保持简单。
其他问题:
.user-home-nav-item
也可以写成 .user-home-nav__item
,如果 item 下有 title,那么就是 .user-home-nav__title
,以避免写成 .user-home-nav__item__title
,但是如果 nav 已有 title .user-home-nav__title
了,或许我们要把 item 下的 title 写成 .user-home-nav__item-title
,总之为了使 BEM 也不至于复杂化,命名上也许还是要纠结一番。所以我最终写成了 .user-home-nav-item
,也许可以这么写下去,没有 B 和 M,trello 貌似就是这样,例如:.attachment-thumbnail-details-options-item-text
。命名空间定义 block 间的关系,这种方式网站上还没有大范围使用,最常见的如 .js-
表示定义 JavaScript 钩子,不用于定义样式。常见命名空间:
.o-layout
。.c-avatar
。.u-hidden
。.t-light
。.s-cms-content
。.is-active
._important
。.js-modal
。例如一个圆形头像组件:
.c-avatar-circle
&__img
display: block
vertical-align: top
max-width: 100%
border-radius: 100%
&__name
text-align: center
以上,BEM 产生的重用性问题还是没有解决,如何解决?
模块化类似于分离的概念,也是分离进化的结果,我们从分离中提取出 .hidden {overflow: hidden}
这样的命名,进而扩展 .avatar {}
这样的命名,不再只包含一条规则。看一下 airbnb 的圆形头像写法:
html:
a.pull-right.media-photo-badge.card-profile-picture.card-profile-picture-offset.is-superhost
.media-photo.media-round
img
CSS:
.card-profile-picture
width: 60px
height: 60px
&-offset
position: relative
top: -40px
margin-bottom: -40px
&.is-superhost
position: relative
img
width: 56px
height: 56px
.media-photo
backface-visibility: hidden
position: relative
display: inline-block
vertical-align: bottom
overflow: hidden
background-color: #bbb
.media-round
border-radius: 50%
border: 2px solid #fff
这个是比较复杂,相对特例的。.media-photo
和 .media-round
是典型的抽象出来的模块,一个定义照片的基本属性,一个定义圆形属性。.card-profile-picture
是特定的命名定义特定具体的值。
-
连接。