- 原文地址:What is Modular CSS?
- 原文做者:Scott Vandehey
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:ssshooter
- 校對者:Hopsken Park-ma
模塊化 CSS 是一組編寫代碼的原則,基於這個原則編寫的代碼具備高性能和可維護性。它起源於雅虎和 Yandex 的開發人員,目的是迎接維護大型代碼庫帶來的挑戰。有些規則在提出之初稍有爭議,但後來被認爲是最佳實踐。css
目錄:html
(偷偷告訴你:若是你對這篇文章的篇幅感到不知所措,觀看視頻可能更適合你,這篇文章來源於此演講。)前端
模塊化 CSS 使用的主要場景是棘手的大規模 CSS。正如 Nicholas Gallagher 所說的:android
來源:Nicholas Gallagher,圖:dotCSSios
這句話直指大規模 CSS 問題的核心。寫代碼並不難,難的是在不讓你的代碼隨着時間的推移成爲拖累你的「技術債」。git
如下是 CSS Guidelines 中的一個示例,這個示例展現了一個問題:除了寫這段代碼的人,沒有人知道這段代碼是幹什麼的。github
<div class="box profile pro-user">
<img class="avatar image" />
<p class="bio">...</p>
</div>
複製代碼
box
和profile
有什麼關係?profile
和avatar
有什麼關係?或者他們之間真的有關係嗎?你應該在bio
旁邊添加pro-user
嗎?image
和profile
寫在同一部分 CSS 嗎?能夠在其餘地方使用avatar
嗎?web
光看代碼沒法回答這些問題,你必須在 CSS 代碼中推理他們的做用。編程
複用代碼會很是棘手。假設你要在另外一個頁面上覆用某個頁面上的樣式,但你想這麼作的時候,會發現那個樣式是專爲第一個頁面而寫的。代碼的做者認爲它只用在某個特定元素中,或者它是從頁面繼承某些類,在其餘環境中根本不起做用。你不想修改原來的內容,而後直接複製了代碼。後端
如今你有兩個問題:一份原始代碼,一份重複代碼,你的維護負擔直接增長了一倍。
大規模的 CSS 也難以維護。你改變了一個標籤,樣式就會像紙牌屋同樣崩潰。你想更新一個頁面上的樣式,卻破壞了另外一個頁面的樣式。你試圖覆蓋其餘頁面,但又深陷於優先度問題。
它讓我想起了我最喜歡的 CSS 笑話之一:
那麼咱們如何解決這些問題呢?答案在於模塊化這個概念,但這是什麼呢?咱們先看看 Harry Roberts 對關注點分離的看法:
來源:Harry Roberts,圖:CSSwizardry.com
這是一個常見編程習慣,可是許多 CSS 開發者不太熟悉。這個思想是確保你所寫的東西不會比你想要作的更多。
舉個例子,說明我在學習模塊化 CSS 以前的工做方式。設計師給我這樣的草圖:
圖:Yandex
我會以爲:「好吧,這是一個書店頁面,側邊欄中有一些小部件,右側列出了大概是書籍封面的清單,一個精選書評,下面還有其餘的評論。」
我當時認爲一個頁面是一個完整的單元,頁面裏的較小部分從屬於頁面。這是一種自上而下的思考方法,這致使大量只服務於單個頁面的一次性代碼,不利於編寫可複用代碼。
圖:Yandex
模塊化 CSS 須要你換一個角度看問題,不從頁面級別考慮,而是關注組成頁面的小塊。這不是一個頁面而是一個組件的集合。
你會發現頁面裏包含的是 logo,搜索欄,導航,照片列表,輔助導航,標籤框,視頻播放器等。這些是能夠網站的任何位置均可以獨立使用的內容。它們只是碰巧在這個特定頁面以這種方式組合。
模塊化 CSS 是自下而上的思惟,須要從構建整個站點的可複用構建模塊開始。
這會讓你想起樂高?應該的!幾乎全部撰寫有關模塊化 CSS 的人都使用樂高進行類比。使用標準化,易於理解,而且不依賴上下文的塊來構建 UI 的是一個很好的思路。
這樣的「塊」最著名的例子之一是由 Nicole Sullivan 定義的「媒體對象」,她認爲這種對象是你將在任何網站上找到的最小的組件之一。
它將固定寬度的圖像組合到靈活寬度的容器的一側,如今處處均可以看到這個模式。她撰寫了一篇名爲 The Media Object Saves Hundreds of Lines of Code 的案例研究,談到將此模式應用於大型網站,最大的例子之一即是 Facebook:
這裏高亮顯示了 Facebook 流中的全部媒體對象。左上角我的信息,右側導航元素,訂閱的每一個帖子,甚至是廣告都是媒體對象。有時它們彼此嵌套。雖然使用目的不一樣,但它們都共享相同的基礎模式:固定寬度的圖像,彈性寬度的文本。
她的觀點是,以 Facebook 的規模運營時,媒體對象就不止幾十個,這樣的頁面上有數百上千個。所以,能夠想象若是爲複用樣式做優化,能夠節省大量代碼,這能夠帶來真正的高性能和低成本。
那麼,既然咱們已經明確了模塊化的概念,那麼讓咱們看看這些年來推崇這一律唸的三大框架:
面向對象的 CSS(Object-Oriented CSS)/ OOCSS 是模塊化 CSS 的起源,由 Nicole Sullivan 於 2009 年提出,這基於她在雅虎的工做。這個框架的核心思想是 —— 對象是可重用的模式(pattern),其視覺外觀不禁上下文決定。
來源:Nicole Sullivan,圖:John Morrison
正如她在 2009 年所定義的那樣,這就是模塊化 CSS 的起源。除此以外,OOCSS 可歸結爲幾個核心原則:
首先,不管你把它放在哪裏,一個對象都應該看起來無差異,不該根據對象的上下文設置對象的樣式。
例如,不是將側邊欄中的全部按鈕都設置爲橙色,將主區域中的全部按鈕設置爲藍色,而是應該建立一個藍色的按鈕類,以及一個橙色的 modifier。這樣作橙色按鈕能夠在任何地方使用,它們沒有被綁定在側邊欄上,它們只是你的按鈕樣式之一。
她談到的另外一個概念,是如何從正在應用的皮膚中抽象出對象的結構。
咱們能夠回到媒體對象的例子。它的樣式與標籤結構無關。有一個容器,一個固定寬度的圖像和內容。你能夠應用不一樣的樣式,可是不管樣式如何改變,標籤結構都是同樣的。
她建議的其餘方法是爲常見的視覺模式建立可複用的類。她給出了一個例子,在 2009 年的亞馬遜網站幾乎全部的東西都有陰影,但由於它們由不一樣設計師創做,因此類似卻不相同。經過標準化這些元素陰影,能夠優化代碼並使網站更高效。
當時她提出了一個很是具備爭議性的規則,但後來被廣爲接受:使用 class 來命名對象及其子元素,這樣能夠在不影響樣式的狀況下修改 HTML 標籤。
她不但願 CSS 由 HTML 標籤來肯定,這樣的話若是將標題從「h1」更改成「h4」,則沒必要更新 CSS。不管選擇哪一個標籤,該標題應該有一個固有的 class。例如,你的導航應該相似於 .site-nav
而不是 #header ul
。
既然建議「老是使用 class」,那麼天然禁止使用 ID 選擇器。這與當時使用 ID 做爲命名空間的常見實踐相違背,直接引用嵌套在其中的元素。
ID 會擾亂 CSS 優先度,這是其次,對象必須是可複用的。根據定義,ID 是惟一的。所以,若是在對象上設置 ID,則沒法在同一頁面上重複使用它,缺乏了模塊化對象的要點。
接下來介紹下一個弘揚模塊化 CSS 精神的框架。BEM,三個字母分別表明 Block、Element、Modifier,BEM 也是在 2009 年提出,起源於 Yandex(能夠說是俄語版的 Google),除搜索業務外還運營網絡郵件程序,所以在編程上他們也須要解決與雅虎相同規模的難題。
他們提出了一套很是相似的代碼原則。他們的核心概念是 —— 塊(block)(Nicole 稱之爲「物體(object)」)由子元素(element)構成,而且能夠修改(modified)(或「主題化」)。
如下是其中一位負責 BEM 的開發者 Varya Stepanova 對 BEM 的描述:
來源:Varya Stepanova,圖:ScotlandJS
BEM 由 3 部分組成:
塊是網頁邏輯和功能的獨立組件。BEM 的發起人對其提出了更詳盡的定義:
首先,塊是可嵌套的。它們應該能被包含在另外一個塊中,而不會破壞任何樣式。例如,可能在側欄中有一個標籤界面小部件的塊,該塊可能包含按鈕,這些按鈕也是一種單獨的塊。按鈕的樣式和選項卡式元素的樣式不會相互影響,一個嵌套在另外一箇中,僅此而已。
其次,塊是可重複的。界面應該可以包含同一塊的多個實例。就像 Nicole 所說的媒體對象同樣,複用塊能夠節省大量代碼。
元素是塊的組成部分,它不能在塊以外使用。一個不錯的例子:一個導航菜單,它包含的項目在菜單的上下文以外沒有意義。你不會爲菜單項定義塊,菜單自己應定義爲塊,而菜單項是其子元素。
修飾符定義塊的外觀和行爲。例如,菜單塊的外觀的垂直或水平,取決於所使用的修飾符。
BEM 所作的另外一件事是定義了很是嚴格的命名約定:
.block-name__element--modifier
這看起來有點複雜,我來分解一下:
-
)分隔__
)分隔--
)分隔這麼說也有點抽象,舉一個例子:
如今咱們有一個標準的樂高 minifig。他是一個藍色的宇航員。咱們將使用 .minifig
類來區分他。
能夠看到 .minifig
塊由較小的元素組成,例如 .minifig__head
和 .minifig__legs
。如今咱們添加一個修飾符:
經過添加 .minifig--red
修飾符,咱們建立了標準藍色宇航員的紅色版本。
或者,咱們可使用 .minifig--yellow-new
修飾符將咱們的宇航員改成新式黃制服版。
你可使用一樣的方式進行更誇張的修改。經過使用 .minifig--batman
修飾符,咱們只用一個類就改變了 minifig 的每一個部分的外觀。
這是實踐中的 BEM 語法例子:
<button class="btn btn--big btn--orange">
<span class="btn__price">$9.99</span>
<span class="btn__text">Subscribe</span>
</button>
複製代碼
即便不看樣式代碼,你也能夠一眼就看出這段代碼會建立一個既大又橙的價格按鈕。不管你是否喜歡帶有連字符和下劃線的這種風格,擁有嚴格的命名約定是模塊化 CSS 向前邁出的一大步,這讓代碼帶有自文檔的效果!
就像 OOCSS 建議使用 class 而不使用 ID 同樣,BEM 也爲代碼風格做了一些限制。最值得注意的是,他們認爲不該該嵌套 CSS 選擇器。嵌套選擇器擾亂了優先度,使得重用代碼變得更加困難。例如,只需使用 .btn__price
而不是 .btn .btn__price
。
注意:這裏的嵌套指實踐中在 Sass 或 Less 嵌套選擇器的作法,但即便你沒有使用預處理器也適用,由於這關乎選擇器優先度問題。
這個原則不出問題是由於嚴格的命名約定。咱們曾經使用嵌套選擇器將它們隔離在命名空間的上下文中。而 BEM 的命名約定自己就提供了命名空間,所以咱們再也不須要嵌套。即便 CSS 的根級別的全部內容都是單個類,但這些名稱的具體程度足以免衝突。
通常來講,選擇器能夠在沒有嵌套的狀況下生效,就不要嵌套它。 BEM 容許此規則的惟一例外是基於塊狀態或其修飾符的樣式元素。例如,可使用 .btn__text
而後用 .btn--orange .btn__text
來覆蓋應用了修飾符按鈕的文本顏色。
咱們最後要討論的框架是 SMACSS,含義是 CSS 的可擴展性和模塊化架構(Scalable & Modular Architecture)。Jonathan Snook 於 2011 年提出了 SMACSS,當時他在雅虎工做,爲 Yahoo Mail 編寫 CSS。
來源:Jonathan Snook,圖:Elida Arrizza
他在 OOCSS 和 BEM 的基礎上添加的關鍵概念是,不一樣類別的組件須要以不一樣的方式處理。
如下是他爲 CSS 系統可能包含的規則定義的類別:
下一個原則是使用前綴來區分類別,他喜歡 BEM 明確的命名約定,但他還但願可以一目瞭然地看出模塊的類型。
l-
用做佈局規則的前綴:l-inline
m-
用做模塊規則的前綴:m-callout
is-
用做狀態規則的前綴:is-collapsed
(基礎規則沒有前綴,由於它們直接應用於 HTML 元素而不使用類。)
這些框架的相同之處遠勝於其不一樣之處。我看到從 OOCSS 到 BEM 再到 SMACSS 的明確發展。它們的發展表明了咱們行業在性能和大規模 CSS 領域不斷增加的經驗。
你沒必要選擇其中一個框架,相反,咱們能夠嘗試定義模塊化 CSS 的通用規則。讓咱們看看這些框架共用和保留的最佳部分。
模塊化系統由如下元素組成:
模塊化系統中的樣式能夠分爲如下幾類:
a
、li
和 h1
.l-centered
、.l-grid
和 .l-fixed-top
.m-profile
、.m-card
和 .m-modal
.is-hidden
、.is-collapsed
和 .is-active
.h-uppercase
、.h-nowrap
和 .h-muted
在模塊化系統中編寫樣式時,請遵循如下規則:
模塊化 CSS 最多見的反對意見就是,它會在 HTML 中產生許多類。我認爲這是由於長期以來 CSS 的最佳實踐都認爲應該避免大量 class 使用。早在 2011 年,Nicole Sullivan 就寫了一篇很棒的博文 Our (CSS) Best Practices are Killing Us,明確駁斥了這個想法。
我看到一些開發人員提倡使用預處理器的 extend
函數將多個樣式鏈接成一個類名。我建議不要這樣作,由於它會使你的代碼不那麼靈活。他們不能讓其餘開發者以新的方式組合你的樂高積木,而是固定了你定義的幾種組合。
不要由於類名太長而懼怕,他們是自文檔的!當我看到 BEM 風格的類名(或任何其餘模塊化命名約定)時,我會以爲很愉悅,由於只要看一眼就能知道這些類的含義。你能夠在 HTML 中清晰理解它們。
長話短說:沒這回事。
模塊化 CSS 初學者能夠快速掌握子元素的概念:minifig__arm
是 minifig
的一部分。然而,有時候他們處理 CSS 中的 DOM 結構時,會疑問如何做深層嵌套,好比 minifig__arm__hand
。
沒有必要這樣作。請記住,這個思路是要將樣式與標記分離。不管 hand
是 minifig
的直接子元素仍是嵌套了多少層,都可有可無。CSS 關心的只有 hand
是 minifig
的孩子。
.minifig {}
.minifig__arm {}
.minifig__arm__hand {} /* don't do this */ .minifig__hand {} /* do this instead */ 複製代碼
模塊化 CSS 初學者比較關注的另外一件事是模塊之間的衝突。例如,若是我將 l-card
模塊和 m-author-profile
模塊同時應用於同一個元素,是否會致使問題?
答案是:理想狀況下,模塊不該該重疊太多。在這個例子中,l-card
模塊關注佈局,而 m-author-profile
模塊關注樣式,你可能會看到 l-card
設置寬度和邊距,而 m-author-profile
設置背景顏色和字體。
測試模塊是否衝突的一種方法是以隨機順序加載它們。你能夠將項目構建配置中設定爲在構建時隨機交換樣式位置。若是看到bug,就證實你的 CSS 須要以特定順序加載。
若是你發現須要將兩個模塊應用於同一個元素而且它們存在衝突,請考慮它們是否真的是兩個獨立的模塊。也許它們能夠用一個修飾符組合成一個模塊?
該規則的最後一個例外是「helper」或「utility」類可能會發生衝突,在這些狀況下,你能夠安全地考慮使用 !important
。我知道,你曾被告知 !important
不是什麼好東西,永遠不該該被使用,但咱們的作法有細微的差異:主動使用它來確保 helper 類老是優先仍是不錯的。 (Harry Roberts has more to say on this topic in the CSS Guidelines。)
咱們來簡要回顧一下,還記得這段代碼嗎?
<div class="box profile pro-user">
<img class="avatar image" />
<p class="bio">...</p>
</div>
複製代碼
box
和profile
有什麼關係?profile
和avatar
有什麼關係?或者他們之間有關係嗎?你應該在bio
旁邊添加pro-user
嗎?image
和profile
寫在同一部分 CSS 嗎?能夠在其餘地方使用avatar
嗎?
如今咱們知道如何解決這些問題了。經過編寫模塊化 CSS 並使用適當的命名約定,咱們能夠編寫自文檔的代碼:
<div class="l-box m-profile m-profile--is-pro-user">
<img class="m-avatar m-profile__image" />
<p class="m-profile__bio">...</p>
</div>
複製代碼
咱們能夠看到哪些類彼此相關,哪些類彼此不相關,以及如何相關。咱們知道在這個組件的範圍以外咱們不能使用哪些類,固然,咱們還知道哪些類能夠在其餘地方複用。
模塊化 CSS 簡化了代碼並推動了重構,產出自文檔的代碼,這樣的代碼不影響外部做用域且可複用。
或者換句話說,模塊化 CSS 是可預測的,可維護的而且是高性能的。
如今咱們能夠重溫那個老笑話,結局發生了變化:
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。