美文网首页前端开发html和cssCSS
BEM —— 前端 CSS 命名方案

BEM —— 前端 CSS 命名方案

作者: 549b968de8fa | 来源:发表于2017-01-17 13:06 被阅读651次

本期介绍的是各大公司前端也都在使用的 BEM 命名方案,目的是为了代码重用。

概述

在该网站的首页就有这么一句话:

BEM — Block Element Modifier is a methodology that helps you to create reusable components and code sharing in front-end development.

翻译过来就是说,BEM 是一种帮助你创建前端中分享可重用的组件与代码的方法。当然,这里说的“分享”其实不是分享的意思哈,而是在多人协作时的用于每个人的代码组织规范。

试想一下,一个前端团队里有 10 个人,每个人都有自己写 ID 和 Class 的一套命名规范,那么你和另一个人是负责 Tab 模块,他是负责 Nav 模块,别的负责 Main 模块。他写的叫 class="page-tab",你写的叫 class="tabs",我不知道哪个写法更好。但是,怎么将你们两个写的代码组织起来呢?人越来越多的时候,那就乱成一团糟了。

当然,这里举的例子不是很好,但是,规范是一个项目团队里一定要有的东西。

如果人靠得住,那么还要什么自动化检查工具?

我后面会讲到 ESLint 这个 JavaScript 代码质量检查工具。我的意思是:代码质量的好坏,决定了多人合作的效率以及后期维护的成本。

而 BEM 命名规范就是为了创建可重用的 HTML 结构与 CSS 代码。不管是你写的,还是他写的,最后都可以单独拿出来放到一个页面中使用,而页面整体的命名是一个风格。

就好比一个班级的规矩或一个公司的 KPI。它是一个衡量标准,它会告诉你,什么样才是最好的。记住,规范的诞生不是说你会变得更好,而是告诉你什么才会更差。我们所熟知的“木桶理论”就是这么个道理,团队中有一个同学代码写的质量不好,审查不过,那么就会拖整个团队进度的后腿。而我们提前就拟定好了规范,那么我们的代码质量就会是一个水平 —— 那就是最好的代码。

好了,前面的废话有点多了。但本质是希望你们明白,代码的质量决定的不是你一个人的事,别人写出味同嚼蜡的代码,对你审阅他人代码而言,那将是地狱;反过来,别人有可能会打你哈哈。


BEM

好,我们正式开始。

BEM,什么是 BEM?

BEM 其实是三个单词的首字母缩写。

  • Block
  • Element
  • Modifier

一会我会一一进行详细的介绍。

我们为什么需要 BEM 命名方式,而不是别的呢?官方给出的说法如下:

无论您选择在项目中使用何种方法,您都将从更结构化的 CSS 和 UI 的优势中受益。当其他风格更容易在团队中理解和适应时,那么这些样式可能不是那么的严格,它会更灵活。

简而言之就是:我选择 BEM 而不是其他方法的原因归结为:它比其他方法(即SMACSS)更少混淆,但仍然为我们提供了我们想要的好的架构(即 OOCSS)和一个可识别的术语。

OOCSS 这个概念大家可能不是很熟悉,而 OOP这个概念大家都听说过,那就是面向对象编程(Object-oriented programming)。什么是面向对象呢?我个人所理解的面向对象就是:比如你要描述一个人,那么他的身上就有很多的特征,如此多的特征组合起来,就是唯一的那一个人。

打个比方,有这么一个男人,他右手总长度 1 米,手掌长 20 厘米,关节长分别是...厘米,上臂长 40 厘米……如此多的细节,我们可以分开描述,每人只测量它一个细节部分,最后将其合并起来,那么他的右手就被详细地描述了出来。

而这里的 OOCSS 也是一样,我们要写一个 Tab 组件,那么我们将其每个细节都拼凑起来,就可以创建出一个具体的 Tab 组件了,这也就是我们常说的粒度。可以将一个具体的东西拆分至不可拆分的地步,再将其组装起来。

就像我们搭积木一样。这也正是前端框架的意义,将每个组件都拆下来,最后使用一定的规范再将它们一一组装起来,这样,大家都写出了一样的代码,后期的维护与修改工作就很好进行了。但,那是工程化的方面,我们现在需要将最基础的东西运用好(HTML、CSS、JavaScript)。


Block

Block,也就是“块”的意思。在我们的 HTML 结构中,什么叫做块呢?

上图中的 headermainasidefooter 我们就可以称为四个块。

如果其中 main 里面,我们还有一个 article 呢?那么这个 article,它也是一个块;如果 article 中还有 section 呢?section 也是一个块……接着就可以一直说到最后一个不可拆分的元素上了。一会再来说。

官方给出的一个解释是:

Standalone entity that is meaningful on its own.(翻译:拥有实际意义的独立实体。)

也就是我们最开始所提到的 组件。比如一个导航栏(Nav)、一个对话框(Dialog)、一个切换框(Tab)等等。当它可以独立存在,并且可以复用到别的页面上,那么它就是一个 Block。

这也是我们所需要它的意义。能将页面元素准确和精确地划分为 N 个模块,那么我们就可以独立进行开发并且独立测试它是否存在 Bug。

另外,我们在以后的其它项目中也可以直接套用这个组件,不需要再花时间重新写一个了。仅仅需要做的就是稍微地更改一下结构(比如 tab 栏再加一栏,nav 区改变下导航中的文字内容等等),再更改一下 CSS 样式(UI)就可以再新的页面中使用了。这样,我们就可以大大节省开发时间。

因此,再 OOCSS 的思想中,我们需要独立出最基础的模块来,未来其它模块只需要在其基础上进行改造即可。

下面,我举个例子。这也是 Boostrap 所做的事情。

我这里有一个 Button 按钮。

<button class="btn">提交</button>
<style>
    .btn {

      width: 80px;
      height: 40px;
      border-radius: 5px;
      border: 1px solid #000;
      background: #fff;
    }
  </style>

它的显示结果如下:



OK,此时我们就可以说,创建了一个 Button 组件。

那么我们下次复用的时候,只需要将其提取出来,再根据设计稿进行更改即可。

我们这里只是更改了它的背景颜色和字体颜色以及内容,具体情况具体分析。

结果如下:


就像上面这样,我们可以将其放入我们的 CSS 文件目录中的 ../button 目录中,创建为 primary-button.csscancel-button.css 两个文件。在未来的使用中,我们会使用一些自动化的前端构建工具将其每个目录下的相关文件合并并打包为一个文件进行加载和传输。

这也就是前端工程化中的一个小部分 —— 独立编写。

那么命名的规范呢?在 Block中,还是和我们平时定义 class 时一样。

<button class="btn">提交</button>

这里是 btn,使用的是它的功能来命名。我们命名时就不可以太具体了。比如 red-btn,这就是十分不合理的,如果我们未来要变成绿的呢?我们就根据它的功能,比如是警示性的按钮,我们就可以使用 btn--warning 等命名方式。另外,不可以将其命名为某一页面中或某一模块中的一员,这样,十分不好维护。比如 main-page-btnarticle-btn等等。这样的命名未来怎么复用呢?因此,在命名前就要考虑好,它是否未来可以作为一个公共组件来使用。如果可以,就使用功能性的命名,是 tab 还是 nav?是 dialog 还是 popup?这需要我们不断地实践和学习。

Block 这一部分还是很简单的,最重要的是划分出什么模块是独立且未来有可能会复用的。


Element

Element,也就是“元素”。什么是元素?这个就更好理解了,我们所用到的构成 Block 块的组成部分就是元素。

官方说法如下:

Parts of a block and have no standalone meaning. Any element is semantically tied to its block.(属于块的一部分,它没有独立的意义。任何元素在语义上都要绑定到它的块上。)

打个比方,当我们描述一个人的脑袋时,都有两个耳朵、一个鼻子、一个嘴对吧?都可以独立成一个 Block,而我们每个人构成耳朵的元素是 DNA 。因此,这些 DNA 就是组成我们耳朵的元素。

在前端中,比方说一个 tab 组件吧。它有一个导航的 ul 菜单和相对应的内容区域。使用上节课学到的 Emmet 语法来试试:

div>(ul>li*4)+(div*4)

结果如下:



此时,假如它就是一个 tab 组件,但这时候我还没有对其进行 ID 和 Class 命名。

那么我们的 Block 命名大家都知道了。这里我们可以使用 class="tabs" 来命名它。


很简单吧,就变成了这样。

在 BEM 命名规范中,Element 的命名规范是使用 __

因此,我再将上面的命名再具体的写下来。

结果如下:


看起来很长对吧,但是这样的命名规范会给你更好保障。另外,你说,那 CSS 怎么写啊!这么恶心,那不是手都要写断了,这时候,我们就可以使用 Sass、Less 或者 SCSS 等(CSS 预处理工具)来进行处理了,我是用的是 Sass,只需要使用 &__item处理后就和tabs__nav__items 一样了。这里先不管这个,我后面会讲这个工具的使用的。

嗯,还有最后一个 Modifier。

Modifier

Modifier,也就是“修饰符”的意思。什么是修饰符呢?修饰符说的就是对这个块(block)或元素(element)的补充说明。

官方给出的说法是:

Flags on blocks or elements. Use them to change appearance, behavior or state.(是块或元素的标志。我们可以使用它们来更改其外观,行为或状态。)

还记得我们上面提到的 button 的例子吗?我写了一个 btn--warning,其中的 --warning 就是我们对这个按钮的修饰,也可以说是一个补充性的说明。

没错,它的写法就是使用:--

这个就比较简单了,还是引用上面的例子,我们上面的例子,只是写到了 Element。一般来说,我们元素的 class 有时候不止一个。一般讲公共样式作为一个 class 属性,独立样式作为另一个 class 属性,而修饰符就是用于独立样式上的修饰。比方说:<div class="tabs tabs--mod"></div>

好,那么我们理解了这个概念,我们来补充我们上面的 Element 中的例子。

我们已经写到了这个样式:



接下来,我就为其添加修饰。

结果如下:


首先,我要声明最好不要像上面那样使用颜色等具体的词语来定义。我这里这么写只是为了告诉大家它的含义,目的是为了修饰它。不得不说,这么写起来很烦,它真的很长。

但是,有一个好处我一定要声明下。使用这样的命名后,它在同一个页面中不会与其他的块起冲突。

我们平时最开始学习写 CSS 代码的时候,我们都是这么写的:

.main-part .tabs {
  /*...*/
}

其实这样的写法是不好的,因为它过度的与页面中的某一部分耦合了。我们在软件工程的思想中追求的就是解耦。我在 ECMAScript 里面说到了一句话“一个函数只做一件事”,我们要将一个功能分离开来,使用几个函数来进行组合与关联;而不是将其一堆写在一个函数中,那样它的可读性与可维护性就丧失了。

现在的 CSS 也是一样。由于过去的代码书写中,我们都是以一个页面来进行开发和维护的,如果有 2 个人合作开发一个页面,假设你写的 header 中的 navclass 定义为 page-nav;而对方在 aside 中也写了这么一个 page-nav,那后果将是多么可怕?打一架,谁打赢了就用谁的,另一个人改?

(以下仅代表个人意见)

现在以及未来的趋势都将是使用组件化、模块化的开发方式。比如我们定义了一个页面,它的结构是 headermainasidefooter。我们只需要定义一个导航栏组件,将其放在我们的 ..css/component/nav/.. 目录中,而页面整体的基本样式以及浏览器重置样式分别放置在 ..css/base 以及 ..css/reset 中。我们在开发时,只需要将 ../nav 目录中的某一个具体的导航栏拿出来,进行相应的样式修改后,再直接放入到我们的页面中即可。

比如:

.tabs--header {
  /*...*/
}
/*以及*/
.tabs--main {
  /*...*/
}

使用以上的方式,就可以直接避免了合作开发中的一些问题,这样,我们就避免了使用 .main-part .tabs 这种可能产生冲突和耦合的结构。它的灵活性将大大增加。


总结

本节讲的东西不单单是命名的方法,也是我个人学习这么久以来的一些个人感悟。其实我们需要知道这么一件事:学习技术不是为了炫技,不是说你写的代码很好就能怎么样;而是为了在多人合作中能有效地组织代码,通过技术来共同努力,改变我们的周遭世界。

相关文章

网友评论

  • c2d4def94f08:之前看过一篇文章说,命名的时候元素最好只能出现一次。嵌套的太多自己看着就蒙逼
  • 源川:就是会太长了点
    549b968de8fa:@Yuanqi828 但是不会造成命名的冲突以及页面耦合的问题,可以使用 CSS 预处理工具解决麻烦哦

本文标题:BEM —— 前端 CSS 命名方案

本文链接:https://www.haomeiwen.com/subject/swrmbttx.html