BEM 是一種模組化元件 CSS 命名規範,最初由 Yandex 團隊所提出。能夠幫助你在開發過程中,增加 class
名稱的複用性與命名上的衝突。
前言
以前寫的 BEM 學習筆記 自己回頭看發現寫跟屎一樣(錯字一堆),但發現還那麼多人看,覺得很丟臉(這就是黑歷史嗎😱),所以最近找時間重新整理了一下。不過我目前是使用 TailwindCSS + Vue CSS scoped,很少寫 BEM 了,有錯誤部分歡迎指教,謝謝。
BEM 概述
1. BEM 是什麼?
BEM 的命名能提供 CSS 以及 HTML 更清晰結構,結合命名空間提供更多訊息,模組化能提高程式碼的複用性,提高後期可維護性。
BEM 名稱來源分别為:
- Block 區塊
- Element 元素
- Modifier 修飾符號
這三個部分的結合稱為 BEM 實體。
2. 遇到問題
首先,我們來看範例,這是常見的 CSS 命名結構:
|
|
|
|
雖然 HTML 看起來美觀,class
名稱有一定語義。但實際上,如果我們不查看 CSS,很難看出 HTML 結構彼此之間的階層關係。而且使用子代選擇器的最大問題在於,之後若有新增元素的需求,容易遇到衝突。
另一種常見的寫法:
|
|
這種寫法的問題:.product
可能在別的頁面也有定義、li
、a
太依賴 HTML 結構,需要層層巢狀。
3. 簡單範例
使用 BEM 的命名規則,上述程式碼將會如下:
|
|
從 HTML 結構上,我們就可以得知,product
與 menu
沒有關係、active
只作用在 li
。我們可以很輕易的新增元素到 menu
中不怕發生衝突。
BEM 的核心就是模組化,讓 class
能夠從上下文中獨立出來。
4. 命名規則
在選擇器中,由以下三種符號來表示擴展的關係:
-
中線:僅作爲連字符號使用,表示某個 Block 或者某個子元素的多單詞之間的連接記號。__
雙底線:用來連接 Block 和 Element。--
雙中線:用來描述一個 Block 或者 Element 的一種狀態。
|
|
BEM 是提供一種規範,具體命名規則可以根據個人偏好選擇:
|
|
任何一種規範,都是便於團隊開發和維護擴展,沒有所謂的「一定要這麼寫」,一切都基於實際需求而定。
BEM 核心
1. Block 區塊
Block 指的是 Web 應用開發中的模組。每個 Block 在邏輯和功能上都是相互獨立具備自己特有的意義。
在大多數情況下,任何獨立的頁面元素(複雜或簡單)都可以被視作一個區塊。它的 HTML 容器會有一個唯一的 class
名稱,也就是這個區塊的名字。
舉例來說,.header
、.menu
、.card
等等。
說明:
- Block 名稱需能清晰的表達出,其用途、功能或意義,具有唯一性。
- 可以加入一些簡短的前綴來達到命名空間的效果,關於命名空間之後會提到。
- 每個 Block 在邏輯上和功能上都相互獨立,在頁面上不能相依其他 Block 或元素。
- Block 可以放置在頁面上的任何位置,可以互相巢狀。
2. Element 元素
Element 為 Block 的一部分並且相依於 Block 的意義,簡單來說就是,如果一個區域不能拿到外部單獨使用,那麼就應該作為一個 Element。
舉例來說,list
和 item
,item
的樣式使否能在其他 Block 中使用,如果不行,就命名成 .list__item
。
說明:
- Element 名稱需能簡單的描述出,其結構、佈局或意義,並且在語義上與 Block 相關聯。
- 不能與 Block 分開單獨使用,並且在頁面上不能相依於其他的 Block 或 Element。
- Element 始終是 Block 的一部分,而不是另一個 Element。這表示 Element 名稱無法定義層次結構,例如
block__elem1__elem2
,之後我們會提到為什麼不行。 - Element 和 Element 之間可以彼此巢狀。
3. Modifier 修飾符號
Modifier 是定義 Block 和 Element 的外觀、狀態或類型。
說明:
- Modifier 需能直觀易懂表達出,其外觀、狀態或行為。
- 不能脱離 Block 或 Element 使用。
- 應該改變的是實體的外觀,行為或狀態,而不是替換它。
- 值可以是 Boolean 或 Key-value 形式。
Boolean 形式,沒加上 .button_active
就是原始樣式:
|
|
如果有多種狀態,則使用 Key-value 形式。就是將其擴展:
|
|
總結
BEM 的優點:
- 語義化,開發時能從 HTML 結構就能看出階層關係。
- 減少選擇器的層層巢狀,有利於渲染效率。
- 不像 OOCSS 它並不是為了處理關於 CSS 全部的模組化,反倒是像是命名空間的概念,透過
class
名稱建立各自獨立的 CSS 模組,並且不會互相干擾,一定程度上,避免命名的污染。
BEM 的缺點:
- 命名方式長而難看,書寫不雅,很多人討厭 BEM 就是因為 HTML 會很醜,但利大於弊。
class
名稱與命名空間、Block 互相依賴,更改名稱的成本較大。不過可以使用 CSS 預處理器改善。
注意:
- 不要使用 ID 選擇器和標籤選擇器。
- 最小化巢狀選擇器。
- 盡量重複使用 Block,將除了最基本的樣式,盡可能挪到 Modifier。
- 使用
class
命名約定來避免命名衝突,並確保名稱具備自解釋性。 - 不要過度模組化。
如何寫 BEM
1. BEM 與命名空間的結合
在 Block 加上命名空間,命名空間可以提升程式碼的可讀性。
每個人使用的命名空間都不同,依個人習慣使用,以下列出常見的:
l-
,Layouto-
,Objecte-
,Elementu-
,Unit
c-
,Componentm-
,Module
u-
,Utilityh-
,Helperf-
,Function
is
/has-
,Statejs-
,JavaScript hook
Layout 佈局
Layout 就是用來定義這些「大架構」的 CSS 或網格系統,例如 l-header
、l-footer
、l-container
、l-grid
。
以排版容器為例:
|
|
Object 物件
Object 物件常見的命名包過 Element 元素、Unit 零件。
Object 們都有著以下的特點:
- 網頁中的最小構建塊,裡面不能包含其他 Object 或 Component;
- 上下文是獨立的。
舉例來說,不放 Icon 的 Button:
|
|
Object 可大可小,以計時器來說,雖然看起來很大,但它裡面如果不包含其他 Object 或 Component,所以可以將它歸類為 Object。
如果要放置其他 Object 或 Component,就必須將他歸類於 Component,例如,Button 內想放置一個 Icon 物件,就需要將它歸類在 Component:
|
|
Components
Component 元件或稱 Module 模組。
Component 有著以下特點:
- 可以包含其他 Object 或 Component。
Utility
Utility 實用工具,也有人使用 Helper 或 Function。
主要是將常用的樣式獨立出來,這類的樣式都會加上 !important
宣告,確保樣式覆蓋上去。
常用的類型:
- 布局類:
margin
、padding
、display
… - 尺寸類:
width
、height
… - 文字類:
color
、font
… - 背景、邊框 … 等等
常見使用縮寫:
p
/m
:margin
/padding
t
/r
/l
/b
:top
/right
/left
/bottom
x
/y
:vertical
/horizontal
bg
:background
Utility 千萬別用以下結構來寫,舉例來說:
|
|
mt
指的是 margin-top
,large
是變化值。但在 BEM 中,Modifier 並不會單獨出現。正確寫法應該是:
|
|
可是這樣就失去了將樣式獨立出來的意義。因此 Utility 可以使用以下結構表示:
|
|
State
State 狀態類表示 Object 或 Component 的當前狀態,通常會搭配 JavaScript 做使用。
使用 BEM,你可能會這樣寫,表示 Card 狀態:
|
|
但這樣你需要為每種狀態去寫大量的 Modifier。
為了保持一致性並減少為 State 設置名稱的認知負擔,可以將以下常見狀態獨立出來,通常會使用 is-
或 has-
前綴表示:
is-active
has-loaded
is-loading
is-visible
is-disabled
is-expanded
is-collapsed
|
|
|
|
|
|
JavaScript hook
JavaScript hook 為 JS 鉤子,可用來表示 DOM 元素是否有使用到 JavaScript。加上 js-
前綴可以讓我們立刻知道這個元素有透過 JS 綁定,避免 CSS 重構破壞 JS 功能。
當然,如果你使用 id
來綁定 DOM 元素,就不需要它。
2. SASS 與 BEM
利用 SASS 的 &
父連接詞與巢狀你可以很輕鬆的寫出 BEM 命名結構。
舉例來說:
|
|
|
|
|
|
當你需要重構名稱時,你只需要修改最上方。
如何給 Modifier 下的 Element 定義規則呢?
假設我們在 c-menu
加上一個 c-menu--lg
,它要改變底下的 Element,那將如何寫呢?
|
|
有些人可能會這樣:
|
|
但這樣改名稱時,要改動的地方變多了,你可以這樣寫:
|
|
寫 BEM 會遇到的問題
1. Element 的子元素
這是最常見的問題。
請考慮以下 HTML 結構:
|
|
當你需要選擇一個巢狀超過兩層的元素,你就會需要用到子孫選擇器,有人會寫成:
|
|
class
名稱就會出現。如果存在多級巢狀結構,你可能就需要重新思考一下你的元件結構。
我們沒必要在 class
名稱中完整呈現 HTML 的結構。
BEM 命名和 DOM 沒有很嚴格的聯繫,所以無論子元素的巢狀程度有多深都沒有關係。命名約定只是用來幫助你識別子元素和頂層元件塊的關係。
回歸最基本的 B__E--M
原則:
|
|
這意味著所有的子元素都僅僅會被 .c-menu
影響,link
不會綁死在 item
下,也可以新增在 list
外。
如果說,Element 子元素無論如何都只能在 Element 內,你可使用 -
分隔,或駝峰式寫法表示:
|
|
2. 巢狀元件的樣式微調
在元件中嵌套元件或物件是一個常見的問題,樣式和位置會受到父級容器的影響。
假設我們想要在上放範例的 c-menu
中加入一個 o-button
。這個按鈕本身已經是一個最基礎的 Object 並且結構如下:
|
|
如果樣式不需要更改,我們可以直接使用。
但如果我們想要讓按鈕變小一點並且完全是圓角,而這些樣式只是 c-menu
元件的一部分。也就是說,當它有一些微小的不同時我們應該怎麼辦?
有以下幾種辦法:
- 使用 BEM 官方的 Mixes 用法。
- 使用 Utility 類型樣式調整。
- 新增 Modifier 來修改。
Mixes 的方式,違反了樣式在各 Block 之間不應該有依賴關係,所以不建議,應該優先使用 Utility 類型,再考慮 新增 Modifier。
3. 過多的 class 名稱
有些人認為每個元素有大量 class
名稱是不好的,一大堆的 Modifier 看了就煩人。
但這並非個問題,因為這意味著程式碼更具有可讀性,你更能清楚的知道它是用來實現什麼的。
舉例來說,這是一個具有三個 class
的按鈕:
|
|
第一眼看到的時候覺得語法不是最簡潔的,但是它非常清晰。
如果你真的不喜歡一大堆的 Modifier。有人提供了一個將狀態與本來的樣式結合,打破了 Modifier 不可以單獨使用的規則,使 HTML 變得更簡潔。
搭配 SASS 的 @extend
使用:
|
|
使用這種方式,可以讓你的程式碼更簡潔:
|
|
雖然脫離了 BEM 本身的原則,但解決了 HTML 的雜亂。
參考資料
- Sass教學 (33) - BEM 設計模式
- 再讀一次 BEM
- CSS 命名規範總結
- CSS 筆記、建議與指導方針總整理
- CSS規範–BEM入門
- CSS BEM 書寫規範
- BEM 規範思維 – 讓 CSS 更利於開發與維護 (一)
- BEM 規範思維 – 讓 CSS 更利於開發與維護 (二)
- 【CSS模塊化之路1】使用BEM與命名空間來規範 CSS
- 使用 BEM 的幾個注意事項
- CSS BEM 解讀
- 快來圍觀BEM方法論
- 和BEM的戰鬥:10個常見問題及如何避免
- Battling BEM CSS: 10 Common Problems And How To Avoid Them
- CSS BEM 命名規範簡介
- 编写模块化 CSS(第 1 部分) - BEM
- 編寫模塊化的CSS(第二部分)—命名空間 | Zell Liew
- 【CSS模塊化之路1】使用BEM與命名空間來規範CSS
- BEM 101
- 《關於 BEM 的反思》
- 為什麼我們要用BEM
- BEM 方法論 - 快速起步 & 重要概念
- CSS Utility Classes: How To Use Them Effectively
- 《More Transparent UI Code with Namespaces》
- 更透明的 UI 代碼和命名空間
- CSS 命名規範
- 關於 css 命名的一點思考,探討一下 css 命名空間的可行性
- HTML語意化及前端架構 About HTML semantics and front-end architecture
- CSS 命名規範
- [譯] 這些 CSS 命名規範將省下你大把調試時間