編寫模組化CSS:BEM
你是否做過多頁面的大型網站或者其中一部分?如果你做過,你可能意識到CSS架構不夠強大所帶來的恐懼。你可能還會研究如何編寫可維護的CSS。
由於我們的行業很棒,我們有很多推薦的解決方案。因為專家們的紛紛加入,於是我們有BEM,OOCSS,SMACSS,Atomic Design等許多選擇。
現在,問題不是痛苦“我不知道該怎麼辦”,而是“有這麼多方法,我應該嘗試哪個?我是不是應該把所有的都用一遍,是不是隻有一種方法才適合我,或者我是不是應該參考它們做一個自己的架構?”。
當我在尋找一個出色的CSS架構時我究竟在找什麼
當我將不同的方法拼湊在一起以形成我自己的習慣時,我會尋找一下四個特點:
- 我必須 立即知道編輯一個
class
是否安全,會不會干擾其他CSS。這是最重要的,特別是當我需要在短時間內進行修改時。我不想因改變一處而破壞別的東西。 - 我必須 立即知道一個
class
放在這個偉大工程的什麼地方,以防止大腦過載,這樣我就可以快速修改style
,而不必在整個工程裡前後引用。 class
必須 儘可能少,因為看到一長串的class
我頭很暈。- 我必須 立即知道一個元件是否使用了 JavaScript,所以如果我改變了它的CSS,我不會以外的破壞了任何元件。
在我的探索中,我發現 BEM 和 名稱空間 符合我尋找的標準
從BEM開始
BEM 是我的方法的基礎。如果你以前從未聽說過BEM,它代表 block
,element
和 modifier
。當你第一次接觸它的時候,他看起來是那麼的難看。
.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