[譯] 什麼是模塊化 CSS?

模塊化 CSS 是一組編寫代碼的原則,基於這個原則編寫的代碼具備高性能和可維護性。它起源於雅虎和 Yandex 的開發人員,目的是迎接維護大型代碼庫帶來的挑戰。有些規則在提出之初稍有爭議,但後來被認爲是最佳實踐。css

目錄:html

  1. 大規模 CSS 的處理難點
  2. 什麼是模塊化
  3. 模塊化框架
    1. OOCSS
    2. BEM
    3. SMACSS
  4. 共享模塊化原則
  5. FAQ
  6. 總結,模塊化 CSS 太美妙啦

(偷偷告訴你:若是你對這篇文章的篇幅感到不知所措,觀看視頻可能更適合你,這篇文章來源於此演講。)前端

大規模 CSS 的處理難點

模塊化 CSS 使用的主要場景是棘手的大規模 CSS。正如 Nicholas Gallagher 所說的android

「Replace ‘can you build this?’ with ‘can you maintain this without losing your minds?’」 —Nicolas Gallagher

來源:Nicholas Gallagher,圖:dotCSSios

這句話直指大規模 CSS 問題的核心。寫代碼並不難,難的是在不讓你的代碼隨着時間的推移成爲拖累你的「技術債」。git

難以理解

如下是 CSS Guidelines 中的一個示例,這個示例展現了一個問題:除了寫這段代碼的人,沒有人知道這段代碼是幹什麼的。github

<div class="box profile pro-user">
  <img class="avatar image" />
  <p class="bio">...</p>
</div>
複製代碼

boxprofile 有什麼關係?profileavatar 有什麼關係?或者他們之間真的有關係嗎?你應該在 bio 旁邊添加 pro-user 嗎?imageprofile 寫在同一部分 CSS 嗎?能夠在其餘地方使用 avatar 嗎?web

光看代碼沒法回答這些問題,你必須在 CSS 代碼中推理他們的做用。編程

難以複用

複用代碼會很是棘手。假設你要在另外一個頁面上覆用某個頁面上的樣式,但你想這麼作的時候,會發現那個樣式是專爲第一個頁面而寫的。代碼的做者認爲它只用在某個特定元素中,或者它是從頁面繼承某些類,在其餘環境中根本不起做用。你不想修改原來的內容,而後直接複製了代碼。後端

如今你有兩個問題:一份原始代碼,一份重複代碼,你的維護負擔直接增長了一倍。

難以維護

大規模的 CSS 也難以維護。你改變了一個標籤,樣式就會像紙牌屋同樣崩潰。你想更新一個頁面上的樣式,卻破壞了另外一個頁面的樣式。你試圖覆蓋其餘頁面,但又深陷於優先度問題。

它讓我想起了我最喜歡的 CSS 笑話之一:

什麼是模塊化

那麼咱們如何解決這些問題呢?答案在於模塊化這個概念,但這是什麼呢?咱們先看看 Harry Roberts關注點分離的看法:

「Code which adheres to the separation of concerns can be much more confidently modified, edited, extended, and maintained because we know how far its responsibilities reach. We know that modifying layout, for example, will only ever modify layout—nothing else.」 —Harry Roberts

來源:Harry Roberts,圖:CSSwizardry.com

這是一個常見編程習慣,可是許多 CSS 開發者不太熟悉。這個思想是確保你所寫的東西不會比你想要作的更多。

舉個例子,說明我在學習模塊化 CSS 以前的工做方式。設計師給我這樣的草圖:

Illustration of a design comp for a bookstore website

圖:Yandex

我會以爲:「好吧,這是一個書店頁面,側邊欄中有一些小部件,右側列出了大概是書籍封面的清單,一個精選書評,下面還有其餘的評論。」

我當時認爲一個頁面是一個完整的單元,頁面裏的較小部分從屬於頁面。這是一種自上而下的思考方法,這致使大量只服務於單個頁面的一次性代碼,不利於編寫可複用代碼。

Illustration of a design comp for a bookstore with the components highlighted

圖:Yandex

模塊化 CSS 須要你換一個角度看問題,不從頁面級別考慮,而是關注組成頁面的小塊。這不是一個頁面而是一個組件的集合。

你會發現頁面裏包含的是 logo,搜索欄,導航,照片列表,輔助導航,標籤框,視頻播放器等。這些是能夠網站的任何位置均可以獨立使用的內容。它們只是碰巧在這個特定頁面以這種方式組合。

模塊化 CSS 是自下而上的思惟,須要從構建整個站點的可複用構建模塊開始。

Image of workers building with Lego bricks

圖:BEM Method

這會讓你想起樂高?應該的!幾乎全部撰寫有關模塊化 CSS 的人都使用樂高進行類比。使用標準化,易於理解,而且不依賴上下文的塊來構建 UI 的是一個很好的思路。

這樣的「塊」最著名的例子之一是由 Nicole Sullivan 定義的「媒體對象」,她認爲這種對象是你將在任何網站上找到的最小的組件之一。

An example of the media object

它將固定寬度的圖像組合到靈活寬度的容器的一側,如今處處均可以看到這個模式。她撰寫了一篇名爲 The Media Object Saves Hundreds of Lines of Code 的案例研究,談到將此模式應用於大型網站,最大的例子之一即是 Facebook:

The media object highlighted in red on the facebook homepage

圖:Nicole Sullivan

這裏高亮顯示了 Facebook 流中的全部媒體對象。左上角我的信息,右側導航元素,訂閱的每一個帖子,甚至是廣告都是媒體對象。有時它們彼此嵌套。雖然使用目的不一樣,但它們都共享相同的基礎模式:固定寬度的圖像,彈性寬度的文本。

她的觀點是,以 Facebook 的規模運營時,媒體對象就不止幾十個,這樣的頁面上有數百上千個。所以,能夠想象若是爲複用樣式做優化,能夠節省大量代碼,這能夠帶來真正的高性能和低成本。

模塊化框架

那麼,既然咱們已經明確了模塊化的概念,那麼讓咱們看看這些年來推崇這一律唸的三大框架:

OOCSS

面向對象的 CSS(Object-Oriented CSS)/ OOCSS 是模塊化 CSS 的起源,由 Nicole Sullivan 於 2009 年提出,這基於她在雅虎的工做。這個框架的核心思想是 —— 對象是可重用的模式(pattern),其視覺外觀不禁上下文決定。

  • 有人質疑雅虎的能力,雅虎的前端團隊當時研發的 YUI library 是很是前沿的技術。在 2009 年,雅虎不是一家沒有前途的科技公司。

「a CSS ‘object’ is a repeating visual pattern, that can be abstracted into an independent snippet of HTML, CSS, and possibly JavaScript. That object can then be reused throughout a site.」 —Nicole Sullivan

來源:Nicole Sullivan,圖:John Morrison

正如她在 2009 年所定義的那樣,這就是模塊化 CSS 的起源。除此以外,OOCSS 可歸結爲幾個核心原則:

上下文無關

首先,不管你把它放在哪裏,一個對象都應該看起來無差異,不該根據對象的上下文設置對象的樣式。

例如,不是將側邊欄中的全部按鈕都設置爲橙色,將主區域中的全部按鈕設置爲藍色,而是應該建立一個藍色的按鈕類,以及一個橙色的 modifier。這樣作橙色按鈕能夠在任何地方使用,它們沒有被綁定在側邊欄上,它們只是你的按鈕樣式之一。

皮膚(主題)

她談到的另外一個概念,是如何從正在應用的皮膚中抽象出對象的結構

咱們能夠回到媒體對象的例子。它的樣式與標籤結構無關。有一個容器,一個固定寬度的圖像和內容。你能夠應用不一樣的樣式,可是不管樣式如何改變,標籤結構都是同樣的。

她建議的其餘方法是爲常見的視覺模式建立可複用的類。她給出了一個例子,在 2009 年的亞馬遜網站幾乎全部的東西都有陰影,但由於它們由不一樣設計師創做,因此類似卻不相同。經過標準化這些元素陰影,能夠優化代碼並使網站更高效。

使用 Class

當時她提出了一個很是具備爭議性的規則,但後來被廣爲接受:使用 class 來命名對象及其子元素,這樣能夠在不影響樣式的狀況下修改 HTML 標籤。

她不但願 CSS 由 HTML 標籤來肯定,這樣的話若是將標題從「h1」更改成「h4」,則沒必要更新 CSS。不管選擇哪一個標籤,該標題應該有一個固有的 class。例如,你的導航應該相似於 .site-nav 而不是 #header ul

不使用 ID

既然建議「老是使用 class」,那麼天然禁止使用 ID 選擇器。這與當時使用 ID 做爲命名空間的常見實踐相違背,直接引用嵌套在其中的元素。

ID 會擾亂 CSS 優先度,這是其次,對象必須是可複用的。根據定義,ID 是惟一的。所以,若是在對象上設置 ID,則沒法在同一頁面上重複使用它,缺乏了模塊化對象的要點。

BEM

接下來介紹下一個弘揚模塊化 CSS 精神的框架。BEM,三個字母分別表明 Block、Element、Modifier,BEM 也是在 2009 年提出,起源於 Yandex(能夠說是俄語版的 Google),除搜索業務外還運營網絡郵件程序,所以在編程上他們也須要解決與雅虎相同規模的難題。

他們提出了一套很是相似的代碼原則。他們的核心概念是 —— 塊(block)(Nicole 稱之爲「物體(object)」)由子元素(element)構成,而且能夠修改(modified)(或「主題化」)。

如下是其中一位負責 BEM 的開發者 Varya Stepanova 對 BEM 的描述:

「BEM is a way to modularize development of web pages. By breaking your web interface into components… you can have your interface divided into independent parts, each one with its own development cycle.」 —Varya Stepanova

來源:Varya Stepanova,圖:ScotlandJS

BEM 由 3 部分組成:

塊(Block)

塊是網頁邏輯和功能的獨立組件。BEM 的發起人對其提出了更詳盡的定義:

首先,塊是可嵌套的。它們應該能被包含在另外一個塊中,而不會破壞任何樣式。例如,可能在側欄中有一個標籤界面小部件的塊,該塊可能包含按鈕,這些按鈕也是一種單獨的塊。按鈕的樣式和選項卡式元素的樣式不會相互影響,一個嵌套在另外一箇中,僅此而已。

其次,塊是可重複的。界面應該可以包含同一塊的多個實例。就像 Nicole 所說的媒體對象同樣,複用塊能夠節省大量代碼。

元素(Element)

元素是塊的組成部分,它不能在塊以外使用。一個不錯的例子:一個導航菜單,它包含的項目在菜單的上下文以外沒有意義。你不會爲菜單項定義塊,菜單自己應定義爲塊,而菜單項是其子元素。

修飾符(Modifier)

修飾符定義塊的外觀和行爲。例如,菜單塊的外觀的垂直或水平,取決於所使用的修飾符。

命名約定

BEM 所作的另外一件事是定義了很是嚴格的命名約定:

.block-name__element--modifier

這看起來有點複雜,我來分解一下:

  • 名稱以小寫字母書寫
  • 名稱中的單詞用連字符(-)分隔
  • 元素由雙下劃線(__)分隔
  • 修飾符由雙連字符(--)分隔

這麼說也有點抽象,舉一個例子:

Example of .minifig to indicate a lego minifig

如今咱們有一個標準的樂高 minifig。他是一個藍色的宇航員。咱們將使用 .minifig 類來區分他。

Example of .minifig module with child elements such as .minifig__head and .minifig__legs

能夠看到 .minifig 塊由較小的元素組成,例如 .minifig__head.minifig__legs。如今咱們添加一個修飾符:

Example of .minifig--red module modifier, turning the minifig red

經過添加 .minifig--red 修飾符,咱們建立了標準藍色宇航員的紅色版本。

Example of a .minifig--yellow-new module modifier, turning the minifig yellow

或者,咱們可使用 .minifig--yellow-new 修飾符將咱們的宇航員改成新式黃制服版。

Example of a .minifig--batman module modifier making a drastic change in the appearance of the minifig

你可使用一樣的方式進行更誇張的修改。經過使用 .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 向前邁出的一大步,這讓代碼帶有自文檔的效果!

不嵌套 CSS

就像 OOCSS 建議使用 class 而不使用 ID 同樣,BEM 也爲代碼風格做了一些限制。最值得注意的是,他們認爲不該該嵌套 CSS 選擇器。嵌套選擇器擾亂了優先度,使得重用代碼變得更加困難。例如,只需使用 .btn__price 而不是 .btn .btn__price

注意:這裏的嵌套指實踐中在 Sass 或 Less 嵌套選擇器的作法,但即便你沒有使用預處理器也適用,由於這關乎選擇器優先度問題。

這個原則不出問題是由於嚴格的命名約定。咱們曾經使用嵌套選擇器將它們隔離在命名空間的上下文中。而 BEM 的命名約定自己就提供了命名空間,所以咱們再也不須要嵌套。即便 CSS 的根級別的全部內容都是單個類,但這些名稱的具體程度足以免衝突。

通常來講,選擇器能夠在沒有嵌套的狀況下生效,就不要嵌套它。 BEM 容許此規則的惟一例外是基於塊狀態或其修飾符的樣式元素。例如,可使用 .btn__text 而後用 .btn--orange .btn__text 來覆蓋應用了修飾符按鈕的文本顏色。

SMACSS

咱們最後要討論的框架是 SMACSS,含義是 CSS 的可擴展性和模塊化架構(Scalable & Modular Architecture)。Jonathan Snook 於 2011 年提出了 SMACSS,當時他在雅虎工做,爲 Yahoo Mail 編寫 CSS。

「At the very core of SMACSS is categorization. By categorizing CSS rules, we begin to see patterns and can define better practices around each of these patterns.」 —Jonathan Snook

來源:Jonathan Snook,圖:Elida Arrizza

他在 OOCSS 和 BEM 的基礎上添加的關鍵概念是,不一樣類別的組件須要以不一樣的方式處理。

類別(Categories)

如下是他爲 CSS 系統可能包含的規則定義的類別:

  1. 基礎(Base) 規則是HTML元素的默認樣式,如連接,段落和標題。
  2. 佈局(Layout) 規則將頁面分紅幾個部分,並將一個或多個模塊組合在一塊兒。它們只定義佈局,而無論顏色或排版。
  3. 模塊(Module)(又名「對象」或「塊」)是可重用的,設計中的一個模塊。例如,按鈕,媒體對象,產品列表等。
  4. 狀態(State) 規則描述了模塊或佈局在特定狀態下的外觀。一般使用 JavaScript 應用或刪除。例如,隱藏,擴展,激活等。
  5. 主題(Theme) 規則描述了模塊或佈局在主題應用時的外觀,例如,在 Yahoo Mail 中,可使用用戶主題,這會影響頁面上的每一個模塊。(這很是適用於像雅虎這樣的應用程序,但大多數網站都不會使用此類別。)

命名約定前綴

下一個原則是使用前綴來區分類別,他喜歡 BEM 明確的命名約定,但他還但願可以一目瞭然地看出模塊的類型。

  • l- 用做佈局規則的前綴:l-inline
  • m- 用做模塊規則的前綴:m-callout
  • is- 用做狀態規則的前綴:is-collapsed

(基礎規則沒有前綴,由於它們直接應用於 HTML 元素而不使用類。)

共享模塊化原則

這些框架的相同之處遠勝於其不一樣之處。我看到從 OOCSS 到 BEM 再到 SMACSS 的明確發展。它們的發展表明了咱們行業在性能和大規模 CSS 領域不斷增加的經驗。

你沒必要選擇其中一個框架,相反,咱們能夠嘗試定義模塊化 CSS 的通用規則。讓咱們看看這些框架共用和保留的最佳部分。

模塊化元素

模塊化系統由如下元素組成:

  • 模塊(Module):(又名對象,塊或組件)一種可複用且自成一體的模式。如媒體對象,導航和頁眉。
  • 子元素(Child Element): 一個不能獨立存在的小塊,屬於模塊的一部分。如媒體對象中的圖像,導航選項卡和頁眉 logo。
  • 模塊修改器(Module Modifier):(又名皮膚或主題)改變模塊的視覺外觀。如左/右對齊的媒體對象,垂直/水平導航。

模塊化類別

模塊化系統中的樣式能夠分爲如下幾類:

  • 基礎(Base) 規則是 HTML 元素的默認樣式,如:alih1
  • 佈局(Layout) 規則控制模塊的佈局方式,但不控制視覺外觀,如:.l-centered.l-grid.l-fixed-top
  • 模塊(Modules) 是可複用的,獨立的 UI 組件視覺樣式,如:.m-profile.m-card.m-modal
  • 狀態(State) 規則由 JavaScript 添加,如:.is-hidden.is-collapsed.is-active
  • 助手(Helper)(又名功能)規則適用範圍小,獨立於模塊,如:.h-uppercase.h-nowrap.h-muted

模塊化規則

在模塊化系統中編寫樣式時,請遵循如下規則:

  • 不要使用 ID
  • CSS 嵌套不要超過一層
  • 爲子元素添加類名
  • 遵循命名約定
  • 爲類名添加前綴

FAQ

這麼作 HTML 不會有不少類嗎?

模塊化 CSS 最多見的反對意見就是,它會在 HTML 中產生許多類。我認爲這是由於長期以來 CSS 的最佳實踐都認爲應該避免大量 class 使用。早在 2011 年,Nicole Sullivan 就寫了一篇很棒的博文 Our (CSS) Best Practices are Killing Us,明確駁斥了這個想法。

我看到一些開發人員提倡使用預處理器的 extend 函數將多個樣式鏈接成一個類名。我建議不要這樣作,由於它會使你的代碼不那麼靈活。他們不能讓其餘開發者以新的方式組合你的樂高積木,而是固定了你定義的幾種組合。

BEM 的類名又長又醜!

不要由於類名太長而懼怕,他們是自文檔的!當我看到 BEM 風格的類名(或任何其餘模塊化命名約定)時,我會以爲很愉悅,由於只要看一眼就能知道這些類的含義。你能夠在 HTML 中清晰理解它們。

孫元素的命名如何約定?

長話短說:沒這回事。

模塊化 CSS 初學者能夠快速掌握子元素的概念:minifig__armminifig 的一部分。然而,有時候他們處理 CSS 中的 DOM 結構時,會疑問如何做深層嵌套,好比 minifig__arm__hand

沒有必要這樣作。請記住,這個思路是要將樣式與標記分離。不管 handminifig 的直接子元素仍是嵌套了多少層,都可有可無。CSS 關心的只有 handminifig 的孩子。

.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。)

總結,模塊化 CSS 太美妙啦

咱們來簡要回顧一下,還記得這段代碼嗎?

<div class="box profile pro-user">
  <img class="avatar image" />
  <p class="bio">...</p>
</div>
複製代碼

boxprofile 有什麼關係?profileavatar 有什麼關係?或者他們之間有關係嗎?你應該在 bio 旁邊添加 pro-user 嗎?imageprofile 寫在同一部分 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 是可預測的,可維護的而且是高性能的。

如今咱們能夠重溫那個老笑話,結局發生了變化:

Two CSS properties walk into a bar. Everything is fine, thanks to modular code and proper namespacing.

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索