BEM(Block-Element-Modifier)

       感謝支持ayqy我的訂閱號,每週義務推送1篇(only unique one)原創精品博文,話題包括但不限於前端、Node、Android、數學(WebGL)、語文(課外書讀後感)、英語(文檔翻譯)        
       若是以爲弱水三千,一瓢太少,能夠去 http://blog.ayqy.net 看個痛快    
   css

一.BEM簡介

BEM自稱是前端開發方法論(Frontend Development Methodology),提供了包括命名規則、CSS/JS模塊化原則、構建工具在內的一套用於開發環節的方法前端

P.S.強調開發環節是爲了與Yeoman之類的東西區分開,BEM想說的是方法,而不是工具web

提供了一套命名規則(BEM命名規則),主要用於模塊化CSS,但BEM中也用在了JS以及文件命名等各個方面編程

CSS中主要解決了這些問題:微信

  • 團隊協做中樣式命名(好比class)衝突app

    用長class名解決,不使用結構化選擇器(如子選擇器、後代選擇器等等),類名自帶層級關係框架

  • 實現代碼本身會說話(self-documenting code)的目標模塊化

    語義化類名,提供更多的信息,例如元素名、功能、所屬組件名等等svn

  • 避免組件之間相互影響工具

    不依賴結構化選擇器,全靠類名

  • 怎樣才能在同一DOM節點上組裝各類特性,同時避免重複代碼(複製粘貼)

    Mix,例如div.classA>div.classB組合Block A和Block B

這些解決方案在如何寫好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

參考資料

  • https://en.bem.info/

聯繫ayqy      

若是在文章中發現了什麼問題,請查看原文並留下評論,ayqy看到就會回覆的(不建議直接回復公衆號,看不到的啦)

特別要緊的問題,能夠直接微信聯繫ayqywx      

(居然發現了師父,之後發佈前得預處理一下了。>_<)

本文分享自微信公衆號 - 前端向後(backward-fe)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息