CSS規範--BEM入門

這段時間在整理前端部分的代碼規範,前面提到的CSS規範裏面會涉及到選擇器的命名,就參考BEM的命名規範,內容整理以下,供你們參考,請斧正!如你們有興趣,可移步至CSS編碼規範css

BEM是由Yandex公司推出的一套CSS命名規範,官方是這麼描述它的:html

BEM是一種讓你能夠快速開發網站並對此進行多年維護的技術。前端

一開始,Yandex公司推出的BEM,包括了規範以及其配套構建工具。現在提到的BEM主要是指其中的規範,在BEM最新的推廣頁中,對其的描述爲:segmentfault

BEM是一種命名方法,可以幫助你在前端開發中實現可複用的組件和代碼共享。框架

BEM解決的問題

css的樣式應用是全局性的,沒有做用域可言。

考慮如下場景:
場景一:開發一個彈窗組件,在現有頁面中測試都沒問題,一段時間後,新需求新頁面,該頁面一打開這個彈窗組件,頁面中樣式都變樣了,一查問題,原來是彈窗組件和該頁面的樣式相互覆蓋了,接下來就是修改覆蓋樣式的選擇器...又一段時間,又開發新頁面,每次爲元素命名都心驚膽戰,求神拜佛,沒寫一條樣式,F5都按多幾回,每一個組件都測試一遍...編輯器

場景二:承接上文,因爲頁面和彈窗樣式衝突了,因此把頁面的衝突樣式的選擇器加上一些結構邏輯,好比子選擇器、標籤選擇器,藉此讓選擇器獨一無二。一段時間後,新同事接手跟進需求,對樣式進行修改,因爲選擇器是一連串的結構邏輯,看不過來,嫌麻煩,就乾脆在樣式文件最後用另外一套選擇器,加上了覆蓋樣式...接下來又有新的需求...最後的結果,一個元素對應多套樣式,遍及整個樣式文件...工具

以往開發組件,咱們都用「重名機率小」或者乾脆起個「當時認爲是獨一無二的名字」來保證樣式不衝突,這是不可靠的。
理想的狀態下,咱們開發一套組件的過程當中,咱們應該能夠隨意的爲其中元素進行命名,而沒必要擔憂它是否與組件之外的樣式發生衝突。測試

BEM解決這一問題的思路在於,因爲項目開發中,每一個組件都是惟一無二的,其名字也是獨一無二的,組件內部元素的名字都加上組件名,並用元素的名字做爲選擇器,天然組件內的樣式就不會與組件外的樣式衝突了。網站

這是經過組件名的惟一性來保證選擇器的惟一性,從而保證樣式不會污染到組件外。編碼

BEM的意思就是塊(block)、元素(element)、修飾符(modifier),是由Yandex團隊提出的一種前端命名方法論。這種巧妙的命名方法讓你的CSS類對其餘開發者來講更加透明並且更有意義。BEM命名約定更加嚴格,並且包含更多的信息,它們用於一個團隊開發一個耗時的大項目。

命名約定的模式以下:

.block {}
.block__element{}
.block--modifier {}
  • .block 表明了更高級別的抽象或組件。

  • .block__element 表明.block的後代,用於造成一個完整的.block的總體。

  • .block--modifier 表明.block的不一樣狀態或不一樣版本。

BEM的關鍵是光憑名字就能夠告訴其餘開發者某個標記是用來幹什麼的。 經過瀏覽HTML代碼中的class屬性,你就可以明白模塊之間是如何關聯的:有一些僅僅是組件,有一些則是這些組件的子孫或者是元素,還有一些是組件的其餘形態或者是修飾符。

咱們用一個類比/模型來思考一下下面的這些元素是怎麼關聯的:

.person {}
.person__hand {}
.person--female {}
.person--female__hand {}
.person__hand--left {}

頂級塊是‘person’,它擁有一些元素,如‘hand’。一我的也會有其餘形態,好比女性,這種形態進而也會擁有它本身的元素。下面咱們把他們寫成‘常規’CSS:

.person {}
.hand {}
.female {}
.female-hand {}
.left-hand {}

這些‘常規’CSS都是有意義的,可是它們之間卻有些脫節。就拿.female來講,是指女性人類仍是某種雌性的動物?還有.hand,是在說一隻鐘錶的指針(譯註:英文中hand有指針的意思)?仍是一隻正在玩紙牌的手?使用BEM咱們能夠得到更多的描述和更加清晰的結構,單單經過咱們代碼中的命名就能知道元素之間的關聯。BEM真是強大。

再來看一個以前用‘常規’方式命名的.site-search的例子:

<form class="site-search  full">
    <input type="text" class="field">
    <input type="Submit" value ="Search" class="button">
</form>

這些CSS類名真是太不精確了,並不能告訴咱們足夠的信息。儘管咱們能夠用它們來完成工做,但它們確實很是含糊不清。用BEM記號法就會是下面這個樣子:

<form class="site-search  site-search--full">
    <input type="text" class="site-search__field">
    <input type="Submit" value ="Search" class="site-search__button">
</form>

從這種CSS的寫法上咱們就已經知道.media__img.media__body必定是位於.media內部的,並且.media__img--rev.media__img的另外一種形態。僅僅經過CSS選擇器的名字咱們就能獲取到以上所有信息。

BEM的另一個好處是針對下面這種狀況:

<div class="media">
    <img src="logo.png" alt="Foo Corp logo" class="img-rev">
    <div class="body">
        <h3 class="alpha">Welcome to Foo Corp</h3>
        <p class="lede">Foo Corp is the best, seriously!</p>
    </div>
</div>

光從上面的代碼來看,咱們根本不明白.media和.alpha兩個class彼此之間是如何相互關聯的?一樣咱們也無從知曉.body和.lede之間,或者.img-rev 和.media之間各是什麼關係?從這段HTML(除非你對那個media對象很是瞭解)中咱們也不知道這個組件是由什麼組成的和它還有什麼其餘的形態。若是咱們用BEM方式重寫這段代碼:

<div class="media">
    <img src="logo.png" alt="Foo Corp logo" class="media__img--rev">
    <div class="media__body">
        <h3 class="alpha">Welcome to Foo Corp</h3>
        <p class="lede">Foo Corp is the best, seriously!</p>
    </div>
</div>

咱們立馬就能明白.media是一個塊,.media__img--rev是一個加了修飾符的.media__img的變體,它是屬於.media的元素。而.media__body是一個還沒有被改變過的也是屬於.media的元素。全部以上這些信息都經過它們的class名稱就能明白,由此看來BEM確實很是實用。

使用BEM常見問題

1 醜極了!

一般人們會認爲BEM這種寫法難看。我敢說,若是你僅僅是由於這種代碼看上去不怎麼好看而羞於使用它.

那麼你將錯失最重要的東西。除非使用BEM讓代碼增長了沒必要要的維護困難,或者這麼作確實讓代碼更難讀了,那麼你在使用它以前就要三思而行了。可是,若是隻是「看起來有點怪」而事實上是一種有效的手段,那麼咱們在開發以前固然應該充分考慮它。是,BEM看上去確實怪怪的,可是它的好處遠遠超過它外觀上的那點瑕疵。

BEM可能看上去有點滑稽,並且有可能致使咱們輸入更長的文本(大部分編輯器都有自動補全功能,並且gzip壓縮將會讓咱們消除對文件體積的擔心),可是它依舊強大。

2. 命名好長?

BEM的命名中包含了模塊名,長長的命名會讓HTML標籤會顯得臃腫。

其實每一個使用BEM的開發團隊多多少少會改變其命名規範,好比Instagram團隊使用的駝峯式:

.blockName-elementName--modifierName { /* ... */ }

還有單下劃線:

.block-name_element-name--modifierName { /* ... */ }

還有修飾器名用單橫線鏈接:

.blockName__elementName-modifierName { /* ... */ }

其實這些對縮短命名沒有多大的幫助,但咱們也無需擔憂文件體積的問題,因爲服務端有gzip壓縮,BEM命名相同的部分多,壓縮下來的體積不會太大。另外如今都用IDE來編寫代碼了,有自動提示功能,也無須擔憂重複的輸入過長的名字。由於命名長,咱們是否是能夠用子代選擇器來代替BEM命名?這樣至少在HTML編寫時,讓HTML標籤看起來美觀一點。

3. 何時用BEM?

當你真正使用BEM的時候,重要的是,請記住你不必真的在每一個地方都用上它。好比:

.caps { 
    text-transform: uppercase; 
}

這條CSS不屬於任何一個BEM範疇,它僅僅只是一條單獨的樣式。

另外一個沒有使用BEM的例子是:

.site-logo {}

這是一個logo,咱們能夠把它寫成BEM格式,像下面這樣:

.header {}
.header__logo {}

但咱們不必這麼作。使用BEM的訣竅是,你要知道何時哪些東西是應該寫成BEM格式的。由於某些東西確實是位於一個塊的內部,但這並不意味它就是BEM中所說的元素。這個例子中,網站logo徹底是恰巧在.header的內部,它也有可能在側邊欄或是頁腳裏面。一個元素的範圍可能開始於任何上下文,所以你要肯定只在你須要用到BEM的地方你才使用它。
再看一個例子:

<div class="content">
    <h1 class="content__headline">Lorem ipsum dolor...</h1>
</div>

在這個例子裏,咱們也許僅僅只須要另外一個class,能夠叫它.headline;它的樣式取決於它是如何被層疊的,由於它在.content的內部;或者它只是恰巧在.content的內部。若是它是後者(即恰巧在.content的內部,而不老是在)咱們就不須要使用BEM

然而,一切都有可能潛在地用到BEM。咱們再來看一下.site-logo的例子,想象一下咱們想要給網站增長一點聖誕節的氣氛,因此咱們想有一個聖誕版的logo。因而咱們有了下面的代碼:

.site-logo {}
.site-logo--xmas {}

咱們能夠經過使用--修飾符來快速地爲咱們的代碼構建另外一個版本。
BEM最難的部分之一是明確做用域是從哪開始和到哪結束的,以及何時使用(不使用)它。隨着接觸的多了,有了經驗積累,你慢慢就會知道怎麼用,這些問題也再也不是問題。

4 你是否是用錯BEM了?

一開始瞭解BEM的時候,可能會產生誤解,出現如下不正確的命名方式:

<div class="page-btn">
    <!-- ... -->
   <ul class="page-btn__list">
       <li class="page-btn__list__item">
           <a href="#" class="page-btn__list__item__link">第一頁</a>
       </li>
   </ul>
   <!-- ... -->
</div>

分頁組件有個ul列表名爲:page-btn__list,列表裏面存放每一頁的按鈕,名爲:page-btn__list__item__link,這是不對的。

首先,有悖BEM命名規範,BEM的命名中只包含三個部分,元素名只佔其中一部分,因此不能出現多個元素名的狀況,因此上述每一頁的按鈕名能夠改爲:page-btn__btn。

而應該以下:

<div class="page-btn">
    <!-- ... -->
   <ul class="page-btn__list">
       <li class="page-btn__item">
           <a href="#" class="page-btn__btn">第一頁</a>
       </li>
   </ul>
   <!-- ... -->
</div>

其次,有悖BEM思想,BEM是不考慮結構的,好比上面的分頁按鈕,即便它是在ul列表裏面,它的命名也不該該考慮其父級元素。當咱們遵循了這個規定,不管父元素名發生改變,或是模塊構造發生的改變,仍是元素之間層級關係互相變更,這些都不會影響元素的名字。

因此即便需求變更了,分頁組件該有按鈕仍是要有按鈕的,DOM構造發生變更,至多也就不一樣元素的增刪減,模塊內名稱也隨之增刪減,而不會出現修更名字的狀況,也就不會由於名字變更,牽涉到JS文件的修改,或樣式文件的修改。

5. 關於BEM修飾器

BEM修飾器表明着元素的狀態,但有時候元素的狀態須要js來控制,此時遵循規範沒有任何好處,好比激活狀態,BEM推薦的寫法是:

.block__element {
    display: none;
}
.block__element--active {
    display: block;

當用js爲該元素添加狀態時,咱們須要知道該元素的名字block__element,這樣咱們才能推導出它的激活狀態爲block__element--active,這是不合理的,由於不少時候咱們沒法得知元素的名稱,因此這時候,咱們應該統一js控制狀態的類名格式,好比is-activejs-active等等,這些類名只用做標識,不予許有默認的公共樣式:

.block__element {
    display: none;
}
.block__element.is-active {
    display: block;
}

6. 關於原子類(短類)與BEM

BEM能夠不須要用到原子類,可是若是已經引入了相似Bootstrap的框架,也不必強制避免使用原子類,好比pull-rightellipsisclearfix等等類,這些類很是實用,和BEM是能夠互補的。

在組件開發中其實不推薦使用原子類,由於這會下降組件的可複用性。可複用性的最理想狀態就是組件不只僅在不一樣的頁面中表現一致,在跨項目的狀況下,也可以運行良好。若是組件的樣式由於依賴於某幾個原子類就要依賴整個Bootstrap庫,那麼組件d 遷移負擔就重不少了。

原子類更適合應用在實際頁面中,這是由於頁面變更大並且不可複用,假設在header中,咱們用到了兩個組件logo和user-panel(用戶操做面板),兩個組件分別置於header的左側和右側,咱們能夠這麼寫:

<div class="header clearfix">
    <div class="logo pull-left"><!-- ... --></div>
    <div class="user-panel pull-left"><!-- ... --></div>
</div>

header能夠封裝成一個模塊,但它複用程度不高,不能算是組件,因此即便使用原子類也沒有關係。在項目中,使用原子類以前應該考慮一下,這個場景是否變更大並且不可複用,若是是的話,咱們能夠放心的使用原子類

組件應該是「自洽的」,其自己就應該構成了一個「生態圈」,也就是說,他幾乎不須要外部供給,自給自足就可以運轉下去。

7. 關於子選擇器

子代選擇器的方式是,經過組件的根節點的名稱來選取子代元素。按照這個思路,分頁按鈕樣式能夠這麼寫:

<div class="page-btn">
   <!-- ... -->
   <ul class="list"></ul>
   <!-- ... -->
</div>
.page-btn { /* ... */ }
.page-btn .list { /* ... */ }

HTML看起來美觀多了,但這解決了樣式衝突問題麼?試想下,若是讓你來接手這個項目,要增長一個需求,新增一個組件,你命名放心麼?

你面臨的問題是:你打開組件目錄,裏面有個分頁組件,叫作page-btn,但是你徹底不知道要怎麼給新組件命名,由於即便新組件模塊名與page-btn不同,也不能保證新組件與分頁組件不衝突。

好比新的需求是「新增一個列表組件」,若是該組件的名字叫作list,其根節點的名字叫list,那麼這個組件下面寫的樣式,就極可能和.page-btn .list的樣式衝突:

.list { /* ... */ }

這還僅僅只有兩個組件而已,實際項目中,十幾個或幾十個組件,難道咱們要每一個組件都檢查一下來「新組件名是否和以往組件的子元素命名衝突了」麼?這不現實。

BEM禁止使用子代選擇器,以上是緣由之一。子代選擇器很差的地方還在於,若是層次關係過長,邏輯不清晰,很是不利於維護。爲了懶得命名或者追求所謂的「精簡代碼」,寫出下面這種選擇器:

.page-btn button:first-child {}
.page-btn ul li a {}
/* ... */
/* 維護代碼,新增需求 */
.page-btn .prev {}

用DOM結構層次關係結構來定位元素,可能會由於需求改變而大面積的重寫樣式文件。試想一下維護這類代碼有多麼痛苦,咱們要一邊檢查該元素的上下文DOM結構,一邊對照着css文件,一一對比,找到該元素對應的樣式,也就是說我爲了改一個元素的代碼,須要不斷翻閱HTML文件和CSS文件,可維護性很是之差。更有甚者,來維護這塊代碼的同事,直接在樣式文件最後添加覆蓋樣式,這會形成一個很是嚴重的問題了:同一個元素樣式零散的分佈在文件的不一樣地方,並且定位該元素的選擇器也可能各不相同

這樣的樣式文件只會越寫越糟糕,能夠說,當咱們用子代選擇器來定位元素時,這個樣式文件就已經註定是要被翻來覆去的重構的了,甚至,每一個來維護這個文件的人都會將其重構一遍。

子代選擇器還會形成權重過大的問題,當咱們要作響應式的時候,某個帶樣式的元素須要適配不一樣的屏幕,此時,咱們還要不斷的確認該元素以前的選擇器寫法!爲了覆蓋前面權重過大的樣式,甚至經過添加額外的類名或標籤名來增長權重。可想而知,此後這個樣式文件的維護難度就像雪球同樣,越滾越大

若是咱們用的是BEM,要覆蓋樣式很簡單:找到要覆蓋樣式的元素,得知它的類名,在媒體查詢中,用它的類名做爲選擇器,寫下覆蓋樣式,樣式就覆蓋成功了,不須要擔憂前面樣式的權重過大。

相關文章
相關標籤/搜索