【CSS模塊化之路1】使用BEM與命名空間來規範CSS

CSS是一門幾十分鐘就能入門,可是卻須要很長的時間才能掌握好的語言。它有着它自身的一些複雜性與侷限性。其中很是重要的一點就是,自己不具有真正的模塊化能力。css

系列文章連接 ↓ ↓html

1. 面臨的問題

CSS中雖然有@import功能。然而,咱們都知道,這裏的@import僅僅是表示引入相應的CSS文件,但其模塊化核心問題並未解決——CSS文件中的任何一個選擇器都會做用在整個文檔範圍裏。前端

所以,其實咱們面臨的最大問題就是——全部的選擇器都是在一個全局做用域內的。一旦引入一個新的CSS文件,就有着與預期不符的樣式表現的風險(由於一些不可預測的選擇器)。java

而現在的前端項目規模愈來愈大,已經不是過去隨便幾個css、js文件就能夠搞定的時代。與此同時的,對於一個大型的應用,前端開發團隊每每也再也不是一兩我的。隨着項目與團隊規模的擴大,甚至是項目過程當中人員的變更,如何更好進行代碼開發的管理已經成爲了一個重要問題。react

回想一下,有多少次:webpack

  • 咱們討論着如何對class進行有效的命名,以免協做開發時的衝突;
  • 咱們面對一段別人寫的css、html代碼,想要去修改,而後瘋狂查找、猜想每一個類都是什麼做用,哪些是能夠去掉的,哪些是能夠修改的——到最後咱們選擇從新添加一個新的class;
  • 咱們準備重構代碼時,重構也就成了重寫
  • ……

用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

2. BEM命名方法論

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">
    ![](avatar.png)
    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">
    ![](avatar.png)
    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有着一些優勢

  • class的單一職責原則、開閉原則
  • 模塊化思想,通常來講遵循這個方法的組件能夠遷移環境
  • 必定程度上,避免命名的污染
  • 自解釋性。能夠直觀看出各個class之間的依賴關係以及它們的做用範圍(.list__item.list__item--underline都是依賴於.list的,所以它們不能脫離於.list存在)

固然,BEM僅僅是一種命名規範或建議。在沒有約束的狀況下,你隨時均可以違反。因此咱們能夠藉助相似BEM-constructor的工具,既幫咱們進行必定的約束,同時也省去一些繁瑣的重複工做。在介紹BEM-constructor以前,咱們還須要簡單瞭解一下BEM-constructor中命名空間(namespaces)的基本概念。

3. 約定項目的命名空間(namespaces)

命名空間(namespaces)也是一種關於CSS中class命名方式的規範。《More Transparent UI Code with Namespaces》提供了一種命名空間的規範。在BEM的基礎上,創建命名空間主要是爲了進一步幫助咱們:

  • 讓代碼可以自解釋
  • 在一個全局的context中安全地加入一個新的class
  • 確保一個修改不會產生額外的反作用
  • 在後期維護時可以迅速定位問題

命名空間分爲如下幾種。

Object: o-

當你使用面向對象的CSS(Object-Oriented CSS)時,o-這個namespace將會很是有用。

  • 對象是一個抽象的概念。
  • 儘可能避免修改它們的樣式。
  • 若是要使用o-時請慎重考慮。

Component: c-

c-應該是一個更爲常見的namespace,表示Components(組件)。

.c-list {}
.c-avatar {}
複製代碼

從命名中咱們就能知道:這是一個list組件;或者這是一個avatar組件。

  • Components應該是一組具體的UI。c-表明一個具體的組件。
  • 修改它們很是安全,只會對組件產生影響。

Utility: u-

Utilities符合單一職責原則,實現一個具體的功能或效果。其概念有些相似JavaScript中的通用工具方法。例如一個清除浮動的Utility,或者一個文字居中的Utility。

.u-clearfix {}
.u-textCenter {}
複製代碼

因爲Utilities做爲一組工具集,在樣式上具備更強的「話語權」,因此!important在Utilities中會更爲常見。當咱們看到下面這段HTML,咱們會更加確信,這個大號的字體是.u-largeFont這個樣式引發的。

<h1 class="title u-largeFont">namespace</h1>
複製代碼
  • Utilities中的樣式通常具備更高的權重.
  • 不要濫用u-前綴,只用在一些通用的工具方法上.

Theme: t-

當咱們使用Stateful Themes這種定義主題的方式時(後續有機會會介紹一些「自定義主題」的方式),每每咱們會在最外層容器元素中加入一個表明不一樣主題的class。這裏就會用到t-

  • 主題t-是一個高層級的命名空間。
  • 必定程度上它和下面的Scope同樣,也爲其內部的規則提供了一個做用空間。
  • 能夠很明顯地標識當前UI的整體狀態(主題)。

Scope: s-

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(內容)。

  • 首先,用到Scopes的場景確實很是的少,所以你準備使用時必定要仔細考慮
  • 它的實現是要依賴於嵌套方式的(SASS/LESS中),也能夠說是CSS後代選擇器

慎用,須要萬分當心。

4. 在SASS中使用BEM-constructor

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 '--'
複製代碼

5. 寫在最後

BEM和namespace是一種命名規範,或者說是一種使用建議。他的目的是幫助咱們寫出更易維護與協做的代碼,更多的是在代碼規範的層面上幫助咱們解決CSS模塊化中的問題。然而,也不得不認可,它距離咱們夢想中的CSS模塊化還有這很長的距離。可是不管如何,其中蘊含的一些組件化與CSS結構組織方式的想法也是值得咱們去思考的。

參考資料

相關文章
相關標籤/搜索