不管你是剛剛發現BEM或者已是個中熟手(做爲web術語來講),你可能已經意識到它是一種有用的方法。若是你還不知道BEM是什麼,我建議你在繼續閱讀這篇文章以前去BEM website瞭解一下它,由於我會假設你對這種CSS的方法有一個基礎的理解。css
本文旨在對那些已是BEM的愛好者或是想要去更有效率的使用它或是很是好奇而且想去學習它的人有所幫助。html
如今,我對BEM是一個優雅的命名方式已經不報有任何幻想。它徹底不是。我曾經很長一段時間放棄接受它的緣由之一就是它的語法看起來很是醜陋。我心中的設計因子不但願我優雅的html結構被醜陋的雙下劃線和連字符弄得一團糟。git
而我心中的開發者因子讓我務實地看待它。最終,這種用來構建用戶界面而且有邏輯性的、模塊化的方式打敗了我右半邊大腦的抱怨:「可是它不夠漂亮!」我固然不會建議你在像起居室這樣小的範圍內使用這種方式,可是當你須要一件救生衣(就像你遨遊在CSS的大海中),我會選擇實用而不是形式。話題拓展的差很少了,如下是10種我已經遇到過的困境和一些如何解決它們的技巧。github
首先來解釋這個問題,當你須要選擇一個嵌套超過兩層的元素,你就會須要用到子孫選擇器。這些選擇器簡直就是個人夢魘,並且我很肯定他們的濫用是人們對BEM產生厭惡的緣由之一,看下面這個例子:web
<div class="c-card"> <div class="c-card__header"> <!-- Here comes the grandchild… --> <h2 class="c-card__header__title">Title text here</h2> </div> <div class="c-card__body"> <img class="c-card__body__img" src="some-img.png" alt="description"> <p class="c-card__body__text">Lorem ipsum dolor sit amet, consectetur</p> <p class="c-card__body__text">Adipiscing elit. <a href="/somelink.html" class="c-card__body__text__link">Pellentesque amet</a> </p> </div> </div>
就像你想的那樣,以這種方式命名會很快就會脫離控制,而且一個組件嵌套的越深,越醜陋也越不可讀的類名就會出現。我已經使用了一個短塊名稱c-card
和短元素名,好比:body
,text
,link
,可是你能夠想象當塊和元素的初始部分被命名爲c-drop-down-menu
會有多麼失控。服務器
我認爲雙下劃線在選擇器名稱中只應該出現一次。BEM表明的是Block__Element--Modifier
,而不是Block__Element__Element--Modifier
。因此,避免多個元素級的命名。若是存在多級嵌套,你可能就須要從新審查一下你的組件結構。app
BEM命名和DOM沒有很嚴格的聯繫,因此不管子元素的嵌套程度有多深都沒有關係。命名約定只是用來幫助你識別子元素和頂層組件塊的關係,在這裏就是c-card
。ide
這是我對相同card組件的處理:模塊化
<div class="c-card"> <div class="c-card__header"> <h2 class="c-card__title">Title text here</h2> </div> <div class="c-card__body"> <img class="c-card__img" src="some-img.png" alt="description"> <p class="c-card__text">Lorem ipsum dolor sit amet, consectetur</p> <p class="c-card__text">Adipiscing elit. <a href="/somelink.html" class="c-card__link">Pellentesque amet</a> </p> </div> </div>
這意味着全部的子元素都僅僅會被card塊影響。因此,咱們能夠將文本和圖片移動到c-card__header
,甚至在不破壞語義結構的狀況下添加一個c-card__footer
元素。佈局
如今,你可能已經注意到個人代碼示例中使用了c-
。這表明「組件」和造成了我命名BEM類名的規範。這個想法來自於致力於提高代碼可讀性的Harry Robert'snamespacing technique
這是我採用的規範,不少前綴會貫穿這篇文章的代碼示例。
TYPE | PREFIX | EXAMPLES |
---|---|---|
Component | c- |
c-card c-checklist |
Layout module | l- |
l-grid l-container |
Helpers | h- |
h-show h-hide |
States | is- has- |
is-visible has-loaded |
JavaScript hooks | js- |
js-tab-switcher |
我發現使用這些命名空間會使個人代碼很是具備可讀性。即便我不能強求你使用BEM,這也絕對是一個值得你使用的關鍵點。
你能夠採用不少其它的命名空間,像qa-
能夠用做質量保證測試,ss-
用做服務器端的鉤子,等等。可是上面的列表是一個好的開始,當你以爲這項技術還不錯,你能夠把它介紹給其餘人。
在下個問題中,會有一個比較實用的關於樣式命名空間的示例。
一些組建須要一個掌控子元素佈局的容器。在這種狀況下,我一般會嘗試把佈局抽象到一個佈局模塊中,好比l-grid
,而且將每個組件做爲l-grid__item
的內容插入。
在咱們card的示例中,若是咱們想要去生成擁有四個c-card
的列表,我會使用下面的html結構:
<ul class="l-grid"> <li class="l-grid__item"> <div class="c-card"> <div class="c-card__header"> […] </div> <div class="c-card__body"> […] </div> </div> </li> <li class="l-grid__item"> <div class="c-card"> <div class="c-card__header"> […] </div> <div class="c-card__body"> […] </div> </div> </li> <li class="l-grid__item"> <div class="c-card"> <div class="c-card__header"> […] </div> <div class="c-card__body"> […] </div> </div> </li> <li class="l-grid__item"> <div class="c-card"> <div class="c-card__header"> […] </div> <div class="c-card__body"> […] </div> </div> </li> </ul>
你如今應該理解佈局模塊和組件的命名空間是如何一塊兒工做的。
不要懼怕使用一些額外的標記會很是使人頭痛。沒有人會拍拍你的背而後告訴你去把<div>
標籤移除掉的。
在一些情景下,佈局模塊是不可能的徹底知足要求的。好比說你的網格沒有能給你想要的結果,或者你只是想要去語義化的命名一個父元素,你應該怎麼作?在不一樣的場景我傾向去選擇contaniner
或者list
。仍是咱們card的例子,我可能會用<div class="l-cards-container">[…]</div>
or <ul class="l-cards-list">[…]</ul>
或者是<ul class="l-cards-list">[…]</ul>
,這取決於使用的條件。關鍵是要和你的命名約定保持一致。
咱們面臨的另外一個常見的問題是組件的樣式和位置會受到父級容器的影響。就這個問題Simurai有不少詳細的解決辦法。我這裏說一個拓展性最好的方式。
假設咱們想要在以前的示例的card__body
中加入一個c-button
。這個按鈕自己已是一個組件而且結構以下:
`<button class="c-button c-button--primary">Click me!</button>`
若是和常規的按鈕組件沒有樣式差異,那麼就沒有問題。咱們只要像下面這樣直接使用:
<div class="c-card"> <div class="c-card__header"> <h2 class="c-card__title">Title text here</h3> </div> <div class="c-card__body"> <img class="c-card__img" src="some-img.png"> <p class="c-card__text">Lorem ipsum dolor sit amet, consectetur</p> <p class="c-card__text">Adipiscing elit. Pellentesque.</p> <!-- Our nested button component --> <button class="c-button c-button--primary">Click me!</button> </div> </div>
舉個例子,若是咱們想要讓按鈕變小一點而且徹底是圓角,而這些樣式只是c-card
組件的一部分。也就是說,當它有一些微小的不一樣時咱們應該怎麼辦?
以前我說過,我找到一個最好用的跨組件類名的解決方式。
<div class="c-card"> <div class="c-card__header"> <h2 class="c-card__title">Title text here</h3> </div> <div class="c-card__body"> <img class="c-card__img" src="some-img.png"> <p class="c-card__text">Lorem ipsum dolor sit amet, consectetur</p> <p class="c-card__text">Adipiscing elit. Pellentesque.</p> <!-- My *old* cross-component approach --> <button class="c-button c-card__c-button">Click me!</button> </div> </div>
這就是BEM官網上著名的「mix」。可是,參考了一些從Esteban Lussich的評價以後,我改變了對這種方式的見解。
在上面的例子中,c-card__c-button
類嘗試去改變一個或多個c-button
中存在的屬性,可是成功應用這些樣式取決於他們的源順序(或者特殊的指定)。c-card__c-button
類只會當它在源代碼裏聲明在c-button
類以後纔會生效。在你構建更多跨組件的組件時會很快失控。(固然,使用!important
也是一種選擇,可是我不建議你這樣作)
一個真正模塊化的UI元素的父元素應該是徹底不可知的-不管你在何處使用它,效果都應該是一致的。像「mix」方式那樣爲另外一個組件添加具備特定樣式的類名,違反了組件驅動設計的開/關原則,即樣式在各模塊之間不該該有依賴關係。
最好的辦法就是在這些微小的樣式差異中使用同一個類,由於你會發現隨着項目的增加你會在別的地方對它進行復用。
`<button class="c-button c-button--rounded c-button--small">Click me!</button>`
即便你不會再使用這些類,爲了應用這些修改,至少也不能把他們和父容器、特殊屬性和源順序耦合在一塊兒。
固然,另外一個選擇就是回到你的設計師崗位,告訴他們這個按鈕應該和網站上的其餘按鈕保持一致,這樣就能夠徹底避免這個問題。但這是另外一碼事了。
決定組件的起止是一個大問題。在c-card
這個示例裏,你可能以後會建立另外一個叫c-panel
的組件,他們兩個樣式相仿,只有一些細微的差異。
可是是什麼決定他們應該是兩個組件呢?c-panel
和c-card
這個塊名,或者僅僅是由於一個修飾器在c-card
裏應用了特殊的樣式。
這樣很容易過分模塊化而且讓一切都變成一個組件。我建議從修飾器開始,可是若是你發現你特定組件的CSS文件正變得很難維護,這時候就能夠中止使用修飾器。當你發現你爲了適應你新的修飾器而不得不去重置這個「塊」全部的CSS時,就是須要新組件的好時機-起碼對我來講是這樣的。
若是你和其它開發者或者設計師協做,最好的方式是去詢問他們的意見而且花幾分鐘去討論。我知道這樣可能有點逃避責任,可是對於一個大型項目來講,理解哪些模塊是可複用的而且在組件的構成上達成一致是相當重要的。
這是一個常見的問題,特別是當你給一個活躍狀態的組件編寫樣式的時候。讓咱們假設cards有一個活躍狀態,當咱們點擊它時,它們會被添加上一個好看的邊框。你會怎麼去命名這個類?
在我看來你有兩種選擇:獨立的狀態鉤或者是一個在組件級相似BEM方式命名的修飾器。
<!-- 獨立狀態勾 --> <div class="c-card is-active"> […] </div> <!-- BEM修飾器 --> <div class="c-card c-card--is-active"> […] </div>
儘管我更傾向於保持一致性的相似BEM的命名方式,獨立類名的好處是使用JavaScript來在任意一個組件中應用通常的狀態鉤更容易。當你不得不使用腳本去應用特定的基於修飾器的狀態類時,類BEM的方式就很讓人頭疼了。這固然是徹底可行的,可是意味着你須要爲每種可能性去編寫更多的JavaScript代碼。
堅持使用一系列標準的狀態鉤是有意義的。Chris Pearece有一個編譯好的列表,我推薦你去了解一下。
我能夠理解不少人在須要構建一個複雜UI的時候面臨的痛苦,特別是他們不習慣去在每一個標籤上添加一個類。
一般,我會在須要特殊樣式的部分上下文添加類名。我會把p
標籤級的捨棄,除非在這個組件中有特殊的需求。
能夠預見,這意味着你的html中會包括很是多類名。最終,你的組件能夠獨立運行而且在沒有反作用的條件下在任何地方使用。
因爲CSS的全局特性,在全部部分都添加類讓咱們能夠徹底控制咱們組件的渲染。最初的心理不適在一整個模塊化的系統完成後是徹底值得的。
假設咱們想要在c-card
組件中展現一個選項列表,下面這是一個反面教材:
<div class="c-card"> <div class="c-card__header"> <h2 class="c-card__title">Title text here</h3> </div> <div class="c-card__body"> <p>I would like to buy:</p> <!-- Uh oh! A nested component --> <ul class="c-card__checklist"> <li class="c-card__checklist__item"> <input id="option_1" type="checkbox" name="checkbox" class="c-card__checklist__input"> <label for="option_1" class="c-card__checklist__label">Apples</label> </li> <li class="c-card__checklist__item"> <input id="option_2" type="checkbox" name="checkbox" class="c-card__checklist__input"> <label for="option_2" class="c-card__checklist__label">Pears</label> </li> </ul> </div> <!-- .c-card__body --> </div> <!-- .c-card -->
這裏有不少問題。第一個是咱們在第一點裏提到的子孫選擇器。第二點是全部應用c-card__checklist__item
樣式都被限定使用,不能複用。
我更傾向於這裏須要打破在這個佈局模塊中的列表自己,而是應該把選項列表單獨抽象成一個組件,這樣就能夠在其它地方獨立使用它們。這裏咱們使用l-
命名空間。
<div class="c-card"> <div class="c-card__header"> <h2 class="c-card__title">Title text here</h3> </div> <div class="c-card__body"><div class="c-card__body"> <p>I would like to buy:</p> <!-- Much nicer - a layout module --> <ul class="l-list"> <li class="l-list__item"> <!-- A reusable nested component --> <div class="c-checkbox"> <input id="option_1" type="checkbox" name="checkbox" class="c-checkbox__input"> <label for="option_1" class="c-checkbox__label">Apples</label> </div> </li> <li class="l-list__item"> <div class="c-checkbox"> <input id="option_2" type="checkbox" name="checkbox" class="c-checkbox__input"> <label for="option_2" class="c-checkbox__label">Pears</label> </div> </li> </ul> <!-- .l-list --> </div> <!-- .c-card__body --> </div> <!-- .c-card -->
這樣你就不用重複哪些樣式,同時也意味着咱們能夠在項目中的其它地方使用l-list
和c-checkbox
。可能這意味着更多的標記,可是對於可讀性,封裝性和可複用性來講代價能夠忽略。你可能已經注意到這些是共同的主題!
有些人認爲每一個元素有大量類名是很差的,--modifiers
會越積越多。就我我的而言,我不認爲這是個問題,由於這意味着代碼更具備可讀性,我也能更清楚的知道它是用來實現什麼的。
舉個例子,這是一個具備四個類的按鈕:
`<button class="c-button c-button--primary c-button--huge is-active">Click me!</button>`
我第一眼看到的時候以爲語法不是最簡潔的,可是很是清晰。
若是這讓你很是頭痛,你能夠查看Sergey Zarouski提出的拓展技術,咱們能夠在樣式表中使用.className [class^="className"]
和[class*=" className"]
來效仿vanilla CSS的拓展功能。若是語法看起來很眼熟,多是由於和Icomoon處理它的icon選擇器的方式很是相似。
使用這種技術,你的代碼可能會看起來像下面這樣:
`<button class="c-button--primary-huge is-active">Click me!</button>`
我不知道使用class^=
和class*=
選擇器是否比獨立的類名錶現更好,可是理論上來講這是一個不錯的選擇。我喜歡使用複合類名,但我以爲這值得那些傾向於尋找替代品的人注意一下。
這是Arie Thulank給我提出的問題,我花費了不少心思去想出一個100%具體具體的解決辦法。
一個例子就是下拉菜單在給定斷點處轉換爲選項卡或者是隱式導航在給定斷點處轉換爲菜單欄。本質上是一個組件在媒體查詢的控制下有兩種不一樣的樣式表現。
我傾向於給這兩個例子去構建一個c-navigation
組件,由於他們在兩個斷點處的行爲本質是相同的。但這讓我陷入沉思,若是是圖片列表在大屏幕上轉化爲輪播圖呢?這對我來講是一個邊緣狀況,只要它有可行的文檔及評論,我認爲這是合理的。但是使用明確的命名(像c-image-list-to-carousel
)來爲這種類型的UI構造一次性的獨立組件。
Harry Roberts寫過一篇響應式後綴來解決這個問題。他的作法是爲了適應更多佈局和樣式的變化,而不是整個組件的變化。但我不明白爲何這項技術不能被應用在這裏。因此,你會發現做者寫的樣式像下面這樣:
`<ul class="c-image-list@small-screen c-carousel@large-screen">`
對於不一樣的屏幕尺寸,這些類就會保留各自的媒體查詢。提示:在CSS中你須要在@
前加上\
來進行轉義,像下面這樣:
.c-image-list\@small-screen { /* styles here */ }
我沒有太多構造這種組件的示例,可是若是你須要構造這種組件,這將是一個對開發者很是友好的方式。下一個加入的人應該能夠輕鬆理解你的想法。我不提倡你使用像small-screen
和large-screen
這樣的命名,他們只是單純爲了可讀性。
BEM在我建立一個模塊化和組件驅動的應用時幫了大忙。我已經使用它大概有三年了,上面的這些問題是我在探索時遇到的阻礙。我但願你認爲這篇文章是有用的。若是你尚未想要體驗BEM,我很是鼓勵你去嘗試一下。
本文根據@David Berner的《Battling BEM (Extended Edition): 10 Common Problems And How To Avoid Them》所譯,整個譯文帶有我本身的理解與思想,若是譯得很差或有不對之處還請多多指點。如需轉載此譯文,需註明英文出處:https://www.smashingmagazine....