##一.BEM 簡介
BEM 自稱是前端開發方法論(Frontend Development Methodology),提供了包括命名規則、CSS/JS 模組化原則、構建工具在內的一套用於開發環節的方法
P.S. 強調開發環節是為了與 Yeoman 之類的東西區分開,BEM 想說的是方法,而不是工具
提供了一套命名規則(BEM 命名規則),主要用於模組化 CSS,但 BEM 中也用在了 JS 以及檔案命名等各個方面
CSS 中主要解決了這些問題:
- 團隊協作中樣式命名(比如 class)衝突
用長 class 名解決,不使用結構化選擇器(如子選擇器、後代選擇器等等),類名自帶層級關係
- 實現程式碼自己會說話(self-documenting code)的目標
語義化類名,提供更多資訊,例如元素名、功能、所屬元件名等等
- 避免元件之間相互影響
不依賴結構化選擇器,全靠類名
- 怎樣才能在同一 DOM 節點上組裝各種特性,同時避免重複程式碼(複製貼上)
Mix,例如 div.classA>div.classB 組合 Block A 和 Block B
這些解決方案在 [如何寫好 CSS](/articles/如何寫好 css/) 都有說明,如果只是定義了一套長類名格式的話,確實沒什麼意義,所以 BEM 還為這一套命名實現了框架和工具,確保開發過程中能用好用
##二。術語概念
(建議跳過,直接看紅色部分,官方解釋等於沒解釋)
###Block
邏輯和頁面功能都獨立的頁面元件,是一個可複用單元,特點如下:
-
可以隨意嵌套組合
-
可以放在任意頁面的任意位置,不影響功能和外觀
-
可複用,介面可以有任意多個相同 Block 的例項
###Element
Block 的組成部分,依賴 Block 存在(出了 Block 就不能用)
###Modifier
[可選] 定義 Block 和 Element 的外觀及行為,就像 HTML 屬性一樣,能讓同一種 Block 看起來不一樣
###BEM entity
上面三個都是
###Mix
單一 DOM 節點上各個 BEM entity 構成的一個例項,特點如下:
-
能把幾個 BEM entity 的行為、樣式結合起來,避免程式碼重複
-
基於現有 BEM entity 語義化地建立新的介面元件
###BEM tree
用 BEM 對 web 頁面結構進行描述
###Block implementation
很多不同的技術決定了 BEM entity 的行為、外觀、測試、模板、文件、依賴描述、額外資料(例如圖片)等方面
###Implementation technology
用來實現一個 Block 的技術,可以是一種或者多種
###Block redefinition
在不同層面上通過給 Block 新增新特性來修改 Block 的實現
###Redefinition level
一系列 BEM entity 及其部分實現
使用中可看作邏輯層級,例如 project level、library level,前者可以重寫後者 Block 的功能及外觀
P.S.實際 是通過檔案目錄和按順序引入實現的層級隔離和重寫
需要關注 的點:
-
B(Block):表示模組,最小的可複用單元,功能獨立,可以嵌套、組合使用
-
E(Element):B 的組成部分
-
M(Modifier):表示 E 的狀態(不同狀態下的 E 有不同的功能和外觀),也是 B 的組成部分
-
BEM tree:用 BEM 術語描述頁面/專案結構
-
Block implementation:實現 Block 需要的東西,包括所有相關內容,比如 JS,CSS,image 等等
-
Redefinition level:可以看作邏輯層級,在高層可以重寫/擴充套件低層 Block(模組)
##三.BEM 命名規則
block-name__element-name_mod-name,例如 nav-menu__item_active
block-name 本身可能並不對應樣式,而只作為 Block 的邏輯名
BEM 只是提供一般方法,並沒有限定必須使用這種命名規則
##四.JavaScript for BEM
###命名規則同樣適用於 JS
JS 中,Modifier 用來表達 Block 或者 Element 的邏輯(CSS 中,Modifier 用來定義外觀),JS 通過一系列狀態來描述 Block 和 Element 的行為
不通過 class 來查詢元件,而是通過 Block 名(HTML 中,不只通過 class 唯一標識,也可以是標籤、屬性等等),例如:
document.querySelector('.button')
.addEventListener('click', function() {
document.querySelector('.popup').classList.toggle('popup_visible');
}, false);
// BEM Style(偽程式碼)
block('button').click(function() {
block('popup').toggleMod('visible');
});
實際應用需要引入 i-bem.js
Modifier 可以設定 Block 的具體狀態,通過新增和移除 Modifier 來實現 Block 的狀態切換,對 Modifier 的操作會觸發事件,通知相關 Block。為了確保這套機制正確執行,不應該允許時隨意切換狀態或者直接修改某個 DOM 節點的 class,這些操作必須通過 BEM 提供的 helper 來實現(這種限制類似於 svn 本地倉���)
Modifier 對應狀態,狀態關聯功能。新增 Modifier 時,狀態發生變化,自動應用相應功能;移除 Modifier 時,相應功能會被自動移除
定義 Modifier 和狀態以及底層之間的關聯:
block('button').onSetMod({
focused: {
true: this.onFocus,
'': this.onBlur
}
});
遮蔽 了狀態之下的東西(可能是新增/移除 Modifier 引起狀態改變,或者是使用者行為觸發的),在程式設計層面需要關注的最小單元是狀態(Modifier)。可以通過給 Modifier 新增樣式來定義各個狀態的外觀,也可以通過 Redefinition level 來改變或者完全重寫 Block 的行為
###把程式碼分離到各檔案中,遵循檔案目錄結構規則
規則:
-
每個 Block 的邏輯和可選的 Element 以及 Modifier 都放在單獨檔案裡
-
每個元件的 JS 檔案目錄結構遵循 BEM 檔案目錄結構規則
示例:
logo/
logo.css # Block's appearance
logo.tmpl # Templates for generating the block's HTML representation
logo.js # Dynamic behavior of the block in the browser
便於管理、複用、移植、支援 Redefinition level 和組合使用
###把邏輯分離到各個 Redefinition level
在不同的 Redefinition level 實現新 Block 可以繼承並擴充套件現有的 Block,或者完全重寫該 Block,例如:
// 完全重寫
block('button').onSetMod({
focused: {
true: this.someCustomOnFocused
}
});
// 部分重寫
block('button').onSetMod({
focused: {
true: function() {
this.__base.apply(this, arguments);
this.someCustomOnFocused();
}
}
});
###原則
JS 中的 Block 與 DOM 節點沒有強對應關係,比如有的 Block 沒有 DOM 表現,只是單純的邏輯塊
Block 之間的互動通過事件訂閱來實現,比如:
-
訂閱其它 Block 例項的事件
-
訂閱 Modifier 變更
-
直接呼叫其它 Block 的公開介面
-
其它任何互動模式,比如 Event Channel(訊息機制)
Element 只能通過所屬的 Block 提供的 API 來訪問,不能 直接訪問某個 Block 中的 Element
##五。檔案目錄結構
程式碼被分解成獨立的小部分,便於開發獨立 Block,釋出前組裝起來進行優化
###特點
- 把 Block implementation 分解到各個單獨的檔案中
便於管理(開發過程中)和移植(植入其它專案)
- 可選的 Element 和 Modifier 都存放在各個單獨的檔案中
只引入相關的 Block implementation(按需載入)
- 檔案按語義分組,而不是檔案型別
比如所有 Block 都放在 block 目錄下,以保證只引入需要的 Block
- 專案被劃分成各 Redefinition level
為了消除重複程式碼,便於重寫/擴充套件現有 Library Block,當 Library Block 更新時,不會影響上層的重寫/擴充套件
###目錄結構示例
blocks/
input/ # input block directory
_type/ # type modifier directory
input_type_search.css # Implementation of modifier type
# with value search in CSS technology
__box/ # box element directory
input__box.css
input.css
input.js
button/ # button block directory
button.css
button.js
button.png
popup/ # popup block directory
_target/
popup_target.css # Common code of modifier target
popup_target_anchor.css # Modifier target with value anchor
popup_target_position.css # Modifier target with value position
_visible/
popup_visible.css # Boolean modifier visible
popup.css
popup.js
檔名也遵循 BEM 命名規則,Modifier 之間的共享部分可以提出來,作為單獨檔案(popup_target.css)
###Redefinition level 示例
library.blocks/
button/
button.css # CSS implementation in the linked library (height 20px)
project.blocks/
button/
button.css # Redefinition of CSS implementation (height 24px)
檔案存放在不同的層級上,分離開避免互相影響
或者按平臺分層:
common.blocks/
button/
button.css # Generic CSS implementation of the button
desktop.blocks/
button/
button.css # Desktop platform-specific button features
mobile.blocks/
button/
button.css # Mobile platform-specific button features
build 過程會自動按層級引入,例如:
@import(common.blocks/button/button.css); /* Generic CSS rules */
@import(desktop.blocks/button/button.css); /* Desktop platform-specific */
##六.build
BEM 專案都有多級檔案結構,build 完成的工作是:
-
把 Block 相關的單獨檔案整合起來
-
按需引入 Block、Element 和 Modifier
-
確保按正確順序引入(保證 Redefinition level)
當然,需要預先定義一些依賴資訊,比如:
-
建立頁面時列出相關的 Block、Element 和 Modifier
-
指明頁面中的 Block、Element 和 Modifier 的依賴關係
依賴宣告可以通過工具完成,比如能夠自動根據 HTML 中應用的 class,生成 BEM Tree,或者根據專案結構生成依賴資訊(非按需生成,所有東西都會被包進來,適用於確定專案目錄下所有東西都是必須的情況,例如類庫),關於依賴宣告方式的更多資訊,請檢視 Declarations in BEM
依賴引入方案與實現有關,比如,CSS 用 @import,JS 用 AMD 等方案
###build 結果
// build 前
blocks/ # Directory containing the blocks
bundles/ # Directory containing all build results
hello/ # Directory for the hello page
hello.decl.js # List of BEM entities required for the hello page
An example of a post-build file structure of a BEM project:
// build 後
blocks/ # Directory containing the blocks
bundles/ # Directory containing all build results
hello/ # Directory for the hello page
hello.decl.js # List of BEM entities required for the hello page
hello.css # Built CSS file for the hello page
hello.js # Built JavaScript file for thehello page
上例中 hello.decl.js 中定義了依賴的元件及其依賴關係
###build tool
官方提供的 build 工具是 ENB,據說很強大:
The BEM platform uses ENB, which is a tool suitable for building BEM projects of any level of complexity.
想要嘗試請檢視新手教程:Starting your own BEM project
###參考資料
暫無評論,快來發表你的看法吧