1. 程式人生 > >編寫模組化CSS:BEM

編寫模組化CSS:BEM

你是否做過多頁面的大型網站或者其中一部分?如果你做過,你可能意識到CSS架構不夠強大所帶來的恐懼。你可能還會研究如何編寫可維護的CSS。

由於我們的行業很棒,我們有很多推薦的解決方案。因為專家們的紛紛加入,於是我們有BEM,OOCSS,SMACSS,Atomic Design等許多選擇。

現在,問題不是痛苦“我不知道該怎麼辦”,而是“有這麼多方法,我應該嘗試哪個?我是不是應該把所有的都用一遍,是不是隻有一種方法才適合我,或者我是不是應該參考它們做一個自己的架構?”

當我在尋找一個出色的CSS架構時我究竟在找什麼

當我將不同的方法拼湊在一起以形成我自己的習慣時,我會尋找一下四個特點:

  • 我必須 立即知道編輯一個 class 是否安全,會不會干擾其他CSS。這是最重要的,特別是當我需要在短時間內進行修改時。我不想因改變一處而破壞別的東西。
  • 我必須 立即知道一個 class 放在這個偉大工程的什麼地方,以防止大腦過載,這樣我就可以快速修改 style,而不必在整個工程裡前後引用。
  • class 必須 儘可能少,因為看到一長串的 class 我頭很暈。
  • 我必須 立即知道一個元件是否使用了 JavaScript,所以如果我改變了它的CSS,我不會以外的破壞了任何元件。

在我的探索中,我發現 BEM名稱空間 符合我尋找的標準

從BEM開始

BEM 是我的方法的基礎。如果你以前從未聽說過BEM,它代表 blockelementmodifier 。當你第一次接觸它的時候,他看起來是那麼的難看。

.block {/* styles */}
.block__element {/* styles */}
.block--modifier {/* styles */}

當我第一次看見 BEM 的時候,我就很討厭他,甚至沒有給他一次機會。我不記得是什麼驅使我嘗試 BEM 的,但我現在深深地知道他有多麼的強大。讓我來完整的解釋一下 BEM 是什麼(當然,包括了我自己的理解)。

一個塊就是一個元件。這有點抽象,讓我們用示例來學習。

假設您在建立一個聯絡表單。在這種情況下,這個表單可以是一個塊。在 BEM 中塊被寫為像 class 的名字一樣,如下所示:

.form {/* styles */}

BEM 使用 .form 而不是 <form> 元素的原因是因為 類允許無限的可重用性,而即使是最基本的元素也可能改變樣式。

按鈕很好的闡釋了可以包含不同樣式的塊。如果將 <button> 元素的背景顏色設定為紅色,則所有的 <buttons> 都將被強制繼承紅色背景。接下來,你必須通過覆蓋你的 <button> 元素來修復程式碼(並有可能在修復過程中“傷及無辜”)。

.button {
  background-color: red;
}

.something button {
  background-color: blue;
}

如果設定了一個 .button 類的按鈕,則可以在任何 <button> 上選擇是否使用 .button 類。那麼,如果你需要一個不同的背景色,你所做的就是改成一個新的 class ,比如說 .button--secondary ,很舒服吧!

.button {
  background-color: red;
}

.button--secondary {
  background-color: blue;
}

這給我們引入了 BEM 的下一部分內容 ——— 修飾符。

修飾符

修飾符是改變某個塊的外觀的標誌。要使用修飾符,可以將 --modifier 新增到塊中。

從上面的按鈕示例繼續,修改的按鈕將被命名為 .button--secondary

在傳統的 BEM 中,當你使用修飾符時,你應該 將塊和修飾符新增 到 HTML 中,以便在新的 .button--secondary 不重寫 .button 樣式。

<button class="button">Primary button</button>
<button class="button button--secondary">Secondary button</button>

.button {
  padding: .5em .75em;
  background-color: red;
}
.button--secondary {
  background-color: blue;
}

注意為什麼沒有在 .button--secondary 中重新宣告 padding ,因為他已經在 .button 中聲明瞭。這很棒,因為 BEM 確保你編寫簡潔的 CSS,而不需要付出大量的工作。

但是,我並不喜歡在 HTML 中再加一個 .button ,因為 .button--modifier 已經告訴我們,他是一個帶有 --secondary 標誌的 .button .理想情況下,我們的 HTML 應該是這樣的:

<button class="button">Primary button</button>
<button class="button--secondary">Secondary button</button>

這更簡潔,不是嗎?

不幸的是,如果 HTML 裡面沒有 .button ,我們必須回到非簡潔的CSS:

.button {
  padding: .5em .75em;
  background-color: red;
}
.button--secondary {
  padding: .5em .75em;
  background-color: blue;
}

額,這麼繁瑣的東西好惡心。但是有兩種方法可以編寫簡潔的CSS,而不需要額外的 class

方法1:使用mixin

第一種方式,如果使用 Sass 或其他與處理器,則 使用mixin來封裝 需要重用的 所有程式碼 。在我們的按鈕示例中,只要將 padding 寫入 mixin。在這裡,我在塊中呼叫這個 mixin:

@mixin button {
  padding: 0.5em 0.75em;
}
.button {
  @include button;
  background-color: red;
}
.button--secondary {
  @include button;
  background-color: green;
}

OK,世界安靜了!

但是,如果我不使用 Sass 怎麼辦?

別緊張!即將分享的第二種方法是使用普通的CSS,所以你也可以使用它。

方法2:使用CSS屬性選擇器

第二種方法 使用CSS屬性選擇器 執行稍微更復雜的選擇。我會告訴你它是什麼,解釋為什麼這樣做:

[class*='button']:not([class*='button__']) {
  padding: 0.5em 0.75em;
}

現在,這不是你通常看到的選擇器,我來解釋一下:

第一部分([class*='button'])告訴解析器查詢包含文字 button 的所有 class 。(*= 搜尋與確切字串匹配的任何內容)。當然,這意味著 CSS 的目標是 .button.button--modifier 。不幸的是,這也意味著選擇器也是針對 BEM 元素,這就是為什麼引入第二部分的原因。

第二部分(:not([class*='button__']))告訴解析器將包含 .button__ 的任何東西排除在外,這樣就排除了 BEM 元素。(BEM 元素具有 .block__element 語法)。

在這一點上,你讓然可能不喜歡 BEM 醜陋的 --modifier 語法。我知道為什麼,但我愛上這個語法的原因是 我很討厭命名 。有時候,我發現需要使用很多單詞來命名一個 BEM 塊或元素。舉個例子 inner-section

因此如果我是用 -modifier(如某些方法簡易的),我將無法一眼看出 -sction 是否是修飾符。所以這是一個餿主意。同樣的,我也不能立即知道 .button-secondary 是否也是修飾符!

很具有諷刺意味,但是這個醜陋的語法讓我們的程式碼更簡潔,更易於維護。所以強烈推薦你嘗試它。

讓我們來看看 BEM 的第三個重要部分 —— 元素。

元素

元素是塊的子節點。為了表明某個東西是一個元素,你需要在塊名後面新增 __element 。所以,你如果看到一個像那樣的名字,比如 form__row ,你將立即知道 .form 塊中有一個 row 元素。

<form class="form" sction="">
  <div class="form__row">
    <!- ... ->
  </div>
</form>

.form__row {
  /* styles */
}

BEM 元素有兩個優點:

  • 你可以讓 CSS 的優先順序保持相對扁平。
  • 你能立即知道那些東西是一個子元素。

為了解釋以上兩點,考慮使用兩個單獨的 class 的替代方法。你可能會用這樣的東西:

<form class="form" section="">
  <div class="row">
    <!- ... ->
  </div>
</form>

.form .row {
  /* styles */
}

如果你使用 BEM 元素,則可以使用優先順序為 10 而不是 20 的選擇起來為 .form__row 提供樣式。此外,你可以立即分辨出(無論是在 HTML 還是 CSS 中).form__row.form 的子節點。

順便說一句,如果你還沒有克服 __element 語法的醜陋,等你使用到第三方外掛看到像蛇一樣的 class 名的時候,你就知道了。

繼續,有件事你需要了解。永遠不應該鏈式命名 BEM 元素 。如果你的 class 最終像這樣 .form__row__input ,你做的事情是非常錯誤的。

有兩種方式可以繞過長長的 BEM 鏈式命名。他們是:

  • 只把子元素連線到有意義的塊。
  • 建立新的塊來儲存元素。

連線孫元素到塊

雖然 BEM 建議你將 BEM 元素寫成 .block__element ,但他們不會規定你的 HTML 應該如何。所以,只要有意義的話,你可以吧你的孫元素連在一起。

接下來是一個例子。在下面的程式碼中,你將看到 .article__header.article 的子元素。 .article__title.article 的孫元素(或者說是 .article__header 的子元素,如果你將它們同時表示為 .article 的子元素,就沒有衝突,因為這個表單同時只有他們存在)。

<article class="article">
  <header class="article__header">
    <h1 class="articel__title"></h1>
  </header>
</article>

雖然這樣有效,但是你也會遇到無意義的連結孫元素的情況。舉個例子:

<section class="comments">
  <h2 class="comments__title"></h2>
  <article class="comments__comment">
    <h3 class="comments__comment-title"></h3>
  </articel>
  <article class="comments__comment">
    <h3 class="comments__comment-title"></h3>
  </article>
</section>

此時你需要建立新的塊來儲存孫元素。

建立新的塊來儲存孫元素

在上述情況下,你可以輕鬆地將 .comments__comment 拆為 .comments.comment

<section class="comments">
  <h2 class="comments__title"></h2>
  <article class="comment">
    <h3 class="comment-title"></h3>
  </article>
    <article class="comment">
    <h3 class="comment-title"></h3>
  </article>
</section>

如果你這樣做,請確保將 .comments.comment 塊放在同一個資料夾中,以方便參考。

不幸的是,有時候它不像 .comments__comment 那麼簡單。例如,假設在塊中有一個列表元素。

<div class="block">
  <ul class="block__list">
    <li class="block__item">
      <!- how would you name this class? ->
      <h3 class="???"></h3>
    </li>
  </ul>
</div>

如果你質疑道,我已經連線了 .block__item ,這是一個 .block 的孫元素。將 .block__item 中的元素連線到 .block 沒有意義,或可能最終會遇到一些糟糕的局面。

然而,同時由於它們被遺棄使用,所以為 .blcok__list.block__item 建立一個新的塊是沒有意義的。你會命名什麼來保持在上下文中有意義?

在這種情況下,我一般會為 block__item 建立一個名為 .item 的偽塊。看下面的 HTML。

<div class="block">
  <h3 class="block__title"></h3>
  <ul class="block__list">
    <li class="block__item">
      <h3 class="item__title"></h3>
    </li>
  </ul>
</div>

偽塊,正如名字所示,是偽的。上面的 HTML 中沒有 .item 的實際宣告。但是,在 .block__item 中有連結到 .item 的元素中。

在我的CSS(Sass)中,我在 .block__item 中巢狀 .item 元素,賦予了它所需的上下文。

.block__item {
  .item__title {
    /* styles */
  }
}

你可能會說,“這是違反 BEM 慣例的!”是的,但請閱讀下一篇文章。你就知道為什麼這樣做。

接下來,還有一件事,在我的用力中新增為 BEM 新增的 —— 容器。

容器

有時(實際上經常),我會遇到這樣的情況,我必須在確定其他元素都對起的同時擴散一個區域的背景色,就像這樣:
這裡寫圖片描述

淺灰色的背景擴散到了對齊的區域的外面

如果你熟悉構建佈局,會使用以下方式構建 HTML:

<section>
  <div class="l-wrap">
    <div class="block">
      <!- ... ->
    </div>
  </div>
</section>

問題是,你應該怎麼命名塊容器?或者在這種情況下,怎麼命名 <section> 元素。我習慣的方法時命名為 block-comtainer 。我只在這種情況下使用 container ,所以我覺得他仍然可以接受。

順便說一下,看見 .l-wrap 中的 .l- 了沒,這就是名稱空間。

總結

所以,這就是我簡單的使用 BEM 的方法。如果你注意到我上面設定的標準,你會注意到我只考慮了兩個方面:

  • class 的數量必須儘可能少。
  • 我必須 立即知道一個 class 放在這個偉大工程中的什麼地方 ,以防止大腦過載。

其他兩方面尚未考慮:

  • 我必須 立即知道元件是否使用 JavaScript
  • 我必須 立即知道編輯一個 class 會否安全,會不會干擾其他CSS