CSS 繼承深度解析

llp0574發表於2016-12-27

我酷愛模組化設計。長期以來我都熱衷於將網站分離成元件,而不是頁面,並且動態地將那些元件合併到介面上。這種做法靈活,高效並且易維護。

但是我不想我的設計看上去是由一些不相關的東西組成的。我是在創造一個介面,而不是一張超現實主義的照片。

很幸運的是,已經有一項叫做 CSS 的技術,就是特意設計用來解決這個問題的。使用 CSS,我就可以在 HTML 元件之間到處傳遞樣式,從而以最小的代價來保證一致性的設計。這很大程度上要感謝兩個 CSS 特性:

  • 繼承
  • 層疊 (CSS 當中的 C,cascade)

儘管這些特性讓我們能夠以一種 DRY 且有效率的方式來給 Web 文件新增樣式,同時也是 CSS 存在的原因,但很明顯,它們已經不再受到青睞。在一些 CSS 方法論裡,如 BEM 和 Atomic CSS 這些通過程式化封裝 CSS 模組的方法,許多都盡力去規避或者抑制這些特性。這也讓開發者有了更多機會去控制他們的 CSS,但這僅僅是一種基於頻繁干預的專項控制。

我準備帶著對模組化介面設計的尊敬在此重新審視繼承、層疊和作用域。我想要的告訴你的是如何利用這些特性讓你的 CSS 程式碼更簡潔,實現更好的自適應,並且提高頁面的可擴充套件性。

繼承和 font-family

儘管許多人在抱怨 CSS 為什麼不單單提供一個全域性作用域,但如果它這麼做的話,那麼就會有很多重複樣式了。反之,CSS 有全域性作用域和區域性作用域。就像在 JavaScript 裡,區域性作用域有許可權訪問父級和全域性作用域,而在 CSS 裡,區域性作用域則幫助了繼承。

例如,如果給根部(也作:全域性)的 html 元素定義一個 font-family 屬性,那麼可以確定這條規則會在文件裡應用到所有祖先元素(有一些例外情況,將在下個部分討論)。

html {
    font-family: sans-serif;
}

/*
This rule is not needed ↷
p {
    font-family: sans-serif;
}
*/

就像在 JavaScript 裡那樣,如果我在區域性作用域裡定義了某些規則,那麼它們在全域性,或者說在任意祖先級的作用域中都是無效的,只有在它們自己的子作用域裡是有效的(就像在上面程式碼中的 p 元素裡)。在下個例子當中,1.5 的 line-height 並沒有被 html 元素用上。但是,p 裡的 a 元素則運用上了 line-height 的值。

    html {
      font-family: sans-serif;
    }

    p {
      line-height: 1.5;
    }

    /*
    This rule is not needed ↷
    p a {
      line-height: 1.5;
    }
    */

繼承最大的好處就是你可以用很少量的程式碼為一致性的視覺化設計建立一個基礎。而且這些樣式甚至將作用到你還沒寫的 HTML 上。我們在討論不會過時的程式碼!

替代方法

當然有另外一種方式提供公用樣式。比如,我可以建立一個 .sans-serif 類…

    .sans-serif {
      font-family: sans-serif;
    }

…並將它應用到任意我想要它有這個樣式的元素上去:

   <p class="sans-serif">Lorem ipsum.</p>

這種方法提供了一些控制上的權利:我可以準確地挑選決定哪些元素應用這個樣式,哪些元素不用。

任何能夠控制的機會都是很吸引人的,但有一些明顯的問題。我不僅需要手動地給需要應用樣式的元素新增類名(這也意味著我要首先確定這個樣式類是什麼效果),而且在這種情況下也已經有效地放棄了支援動態內容的可能性:不管是富文字編輯器還是 Markdown 解析器都沒辦法給任意的 p 元素提供 sans-serif 類。

class="sans-serif" 和 style="font-family: sans-serif" 的用法差不多 – 除了前者意味著要同時在樣式表和 HTML 當中新增程式碼。使用繼承,我們就可以在其中一個少寫點,而另外一個則不用再寫了。相比給每個字型樣式寫一個類,我們可以只在一個宣告裡,給 html 元素新增想要的規則。

    html {
      font-size: 125%;
      font-family: sans-serif;
      line-height: 1.5;
      color: #222;
    }

inherit 關鍵字

某些型別的屬性是不會預設繼承的,而某些元素則不會繼承某些屬性。但是在某些情況下,可以使用 [property name]: inherit 來強制繼承。

舉個例子,input 元素在之前的例子中不會繼承任何字型的屬性,textarea 也一樣不會繼承。為了確保所有元素都可以從全域性作用域中繼承這些屬性,可以使用通配選擇符和 inherit 關鍵字。這樣,就可以最大程度地使用繼承了。

    * {
      font-family: inherit;
      line-height: inherit;
      color: inherit;
    }

    html {
      font-size: 125%;
      font-family: sans-serif;
      line-height: 1.5;
      color: #222;
    }

注意到我忽略了 font-size。我不想直接繼承 font-size 的原因是,它會將 heading 元素(譯者注:如 h1)、small 元素以及其他一些元素的預設 user-agent 樣式給覆蓋掉。這麼做我就可以節省一行程式碼,並且讓 user-agent 決定想要什麼樣式。

另外一個我不想繼承的屬性是 font-style:我不想重設 em 的斜體,然後再次新增上它。這將成為無謂的工作並會產生多餘的程式碼。

現在,所有不管是可以繼承或者是強制繼承的字型樣式都是我所期望的。我們已經花了很長時間只用兩個宣告區塊來傳遞一個一致性的理念和作用域。從現在開始,除開一些例外情況,沒有人會在構造元件的時候還需要去考慮 font-familyline-height 或者 color 了。這就是層疊的由來。

基於例外的樣式

我可能想要主要的 heading 元素(h1)採用相同的 font-familycolor 和 line-height。使用繼承就是很好的解決方案,但是我又想要它的 font-size 不一樣。因為預設的 user-agent 樣式已經給 h1 元素提供了一個大號的 font-size(但這時它就會被我設定的相對基礎字型大小為 125% 的樣式覆蓋掉),可能的話我不需要這裡發生覆蓋。

然而,難道我需要調整所有元素的字型大小嗎?這時我就利用了全域性作用域的優勢,在區域性作用域裡只調整我需要調整的地方。

    * {
      font-family: inherit;
      line-height: inherit;
      color: inherit;
    }

    html {
      font-size: 125%;
      font-family: sans-serif;
      line-height: 1.5;
      color: #222;
    }

    h1 {
      font-size: 3rem;
    }

如果 CSS 元素的樣式預設被封裝,那麼下面的情況就不可能了:需要明確地給 h1 新增所有字型樣式。反而,我可以將樣式分為幾個單獨的樣式類,然後通過空格分隔來逐一給 h1 新增樣式:

  <h1 class="Ff(sans) Fs(3) Lh(1point5) C(darkGrey)">Hello World</h1>

不管哪種方式,都需要更多的工作,而且最終目的都是一個具備樣式的 h1。使用層疊,我已經給大部分元素賦上了想要的樣式,並且只在一個方面使得 h1 成為一個例外。層疊作為一個過濾器,意味著樣式只在新增新樣式覆蓋的時候才會發生改變。

元素樣式

我們已經開了個好頭,但想要真正地掌握層疊,還需要儘可能多地給公共元素新增樣式。為什麼?因為我們的混合元件是由獨立的 HTML 元素構成,並且一個螢幕閱讀器友好的介面充分利用了語義化結構標記。

換句話說,讓你的介面“分子化”(使用了 atomic 設計術語)的 “atoms” 樣式應該在很大程度上可定位並且使用元素選擇符。元素選擇符的優先順序很低,所以它們不會覆蓋你之後可能加進來的基於類的樣式。

首先應該做的事情就是給所有你即將需要使用的元素新增樣式:

    a { … }
    p { … }
    h1, h2, h3 { … }
    input, textarea { … }
    /* etc */

如果你想在無冗餘的情況下有個一致性介面的話,那麼下一步非常重要:每當你建立一個新元件的時候,如果它採用了一些新元素,那麼就用元素選擇符來給它們新增樣式。現在不是時候去使用限制性、高優先順序的選擇符,也沒有任何需要去編寫一個樣式類。語義化元素就使用其本身。

舉個例子,如果我還沒有給 button 元素 (就像前一個例子)新增樣式,並且新元件加入了一個 button 元素,那麼這就是一個給整個介面的 button 元素新增樣式的好機會。

    button {
      padding: 0.75em;
      background: #008;
      color: #fff;
    }

    button:focus {
      outline: 0.25em solid #dd0;
    }

現在,當你想要再寫一個新元件並且同樣加入按鈕的時候,就少了一件需要操心的事情了。在不同的名稱空間下,不要去重寫相同的 CSS,並且也沒有類名需要記住或編寫。CSS 本就應該總是致力於讓事情變得簡單和高效 – 它本身就是為此而設計的。

使用元素選擇符有三個主要的優勢:

  • 生成的 HTML 更加簡潔(沒有多餘的各種樣式類)。
  • 生成的樣式表更加簡潔(樣式在元件間共享,不需要在每個元件裡重寫)。
  • 生成的新增好樣式的介面基於語義化 HTML。

使用類來專門提供樣式常常被定義為“關注點分離”。這是對 W3C 的關注點分離原則的誤解。它的目的是用 HTML 和 CSS 樣式來描述整個結構。因為類專門是為了樣式目的而制定,而且是在結構標記裡出現,所以無論它們在哪裡使用,技術上都是在打破分離,你不得不改變實質結構來得到樣式。

不管在哪裡都不要依賴表面的結構標記(樣式類,內聯樣式),你的 CSS 應該相容通用的結構和語義化的約定。這就可以簡單地擴充套件內容和功能而無需它也變成一個樣式的任務。同樣在不同傳統語義化結構的專案裡,也可以讓你的 CSS 變得更加可複用(但是這一點 CSS 的“方法論”可能會有所不同)。

特殊情況

在有人指責我過分簡單化之前,我意識到介面上不是所有的按鈕都做同樣的事情,我還意識到做不同事情的按鈕在某種程度上可能應該看起來不一樣。

但這並不是說我們就需要用樣式類、繼承或者層疊來處理了。讓一個介面上的按鈕看起來完全不一樣是在混淆你的使用者。為了可訪問性和一致性,大多數按鈕在外觀上只需要通過標籤來進行區分。

    <button>create</button>

    <button>edit</button>

    <button>delete</button>

記住樣式並不是視覺上唯一的區分方法。內容同樣可以在視覺上區分,而且在一定程度上它更加明確一些,因為你可是在文字上告訴了使用者不同的地方。

大多數情況下,單獨使用樣式來區分內容都不是必要或者正確的。通常,樣式區分應該是附加條件,比如一個紅色背景或者一個帶圖示的文字標籤。文字標籤對那些使用聲音啟用的軟體有著特定的效果:當說出 “red button” 或者 “button with cross icon” 的時候並沒有引起軟體的識別時。

我將在“工具類”部分探討關於新增細微差別到看起來相似的元素上的話題。

標籤屬性

語義化 HTML 並不僅僅關於元素。標籤屬性定義型別、樣式屬性和狀態。這些對可訪問性來說也很重要,所以它們需要寫在 HTML 裡合適的地方。而且因為都在 HTML 裡,所以它們還提供了做樣式鉤子的機會。

舉個例子,input 元素有一個 type 屬性,那麼你應該想要利用它的好處,還有像 aria-invalid 屬性是用來描述狀態的。

    input, textarea {
      border: 2px solid;
      padding: 0.5rem;
    }

    [aria-invalid] {
      border-color: #c00;
      padding-right: 1.5rem;
      background: url(images/cross.svg) no-repeat center 0.5em;
    }

這裡有幾點需要注意一下:

  • 這裡我不需要設定 colorfont-family 或者 line-height,因為這些都從 html 上繼承了,得益於上面使用的 inherit關鍵字。如果我想在整個應用的層面上改變 font-family,只需要在 html 那一塊對其中一個宣告進行編輯就可以了。
  • border 的顏色關聯到 color,所以它同樣是從全域性 color 中繼承。我只需宣告 border 的寬度和風格。
  • [aria-invalid] 屬性選擇符是沒有限制的。這意味著它有著更好的應用(它可以同時作用在 input 和 textarea 選擇符)以及最低的優先順序。簡單的屬性選擇符和類選擇符有著同樣的優先順序。無限制使用它們意味著之後任何寫在層疊下的樣式類都可以覆蓋它們。

BEM 方法論通過一個修飾符類來解決這個問題,比如 input--invalid。但是考慮到無效的狀態應該只在可通訊的時候起作用,input--invalid 還是一定的冗餘。換句話說,aria-invalid 屬性不得不寫在那裡,所以這個樣式類的目的在哪裡?

只寫 HTML

在層疊方面關於大多數元素和屬性選擇符我絕對喜歡的事情是:元件的構造變成更少地瞭解公司或組織的命名約定,更多地關注 HTML。任何精通寫出像樣 HTML 的開發者被分配到專案中時,都會從已經寫到位的繼承樣式當中獲益。這些樣式顯著地減少了讀文件和寫新 CSS 的需要。大多數情況下,他們可以只寫一些死記硬背應該知道的(meta)語言。Tim Baxter 同樣為此在 Meaningful CSS: Style It Like You Mean It 裡寫了一個案例。

佈局

目前為止,我們還沒有寫任何指定元件的 CSS,但這並不是說我們還沒有新增任何相關樣式。所有元件都是 HTML 元素的組合。形成更復雜的元件主要是靠這些元素的組合順序和排列。

這就給我們引出了佈局這個概念。

主要我們需要處理流式佈局 – 連續塊元素之間的間距。你可能已經注意到目前為止我沒有給任何元素設定任何的外邊距。那是因為外邊距不應該考慮成一個元素的屬性,而應該是元素上下文的屬性。也就是說,它們應該只在遇到元素的時候才起作用。

幸運的是,直接相鄰選擇符可以準確地描述這種關係。利用層疊,我們可以使用一個統一預設貫穿所有連續塊級元素的選擇符,只有少數例外情況。

    * {
      margin: 0;
    }

    * + * {
      margin-top: 1.5em;
    }

    body, br, li, dt, dd, th, td, option {
      margin-top: 0;
    }

使用優先順序極低的貓頭鷹選擇符確保了任意元素(除了那些公共的例外情況)都通過一行來間隔。這意味著在所有情況下都會有一個預設的白色間隔,所有編寫元件流內容的開發者都將有一個合理的起點。

在大多數情況下,外邊距只會關心它們自己。不過因為低優先順序,很輕易就可以在需要的時候覆蓋掉那基礎的一行間隔。舉個例子,我可能想要去掉標籤和其相關元素之間的間隔,好表示它們是一對的。在下面的示例裡,任意在標籤之後的元素(inputtextareaselect 等等)都不會有間隔。

    label {
      display: block
    }

    label + * {
      margin-top: 0.5rem;
    }

再次,使用層疊意味著只需要在需要的時候寫一些特定的樣式就可以了,而其他的元素都符合一個合理的基準。

需要注意的是,因為外邊距只在元素之間出現,所以它們不會和可能包括在容器內的內邊距重疊。這也是一件不需要擔心或者預防的事情。

還注意到不管你是否決定引入包裝元素都得到了同樣的間隔。就是說,你可以像下面這樣做並實現相同的佈局 – 外邊距在 div之間出現比在標籤和輸入框之間出現要好得多。

    <form>
      <div>
        <label for="one">Label one</label>
        <input id="one" name="one" type="text">
      </div>
      <div>
        <label for="two">Label two</label>
        <input id="two" name="two" type="text">
      </div>
      <button type="submit">Submit</button>
    </form>

用像 atomic CSS 這樣的方法能實現同樣的效果,只需組合各種外邊距相關的樣式類並在各種情況下手動新增它們,包括被 * + * 隱式控制的 first-child 這種例外情況:

    <form class="Mtop(1point5)">
      <div class="Mtop(0)">
        <label for="one" class="Mtop(0)">Label one</label>
        <input id="one" name="one" type="text" class="Mtop(0point75)">
      </div>
      <div class="Mtop(1point5)">
        <label for="two" class="Mtop(0)">Label two</label>
        <input id="two" name="two" type="text" class="Mtop(0point75)">
      </div>
      <button type="submit" class="Mtop(1point5)">Submit</button>
    </form>

記住如果堅持使用 atomic CSS 的話,像上面那麼寫只會覆蓋到頂部外邊距的情況。你必須還要為 colorbackground-color以及其他屬性建立獨立的樣式類,因為 atomic CSS 不會控制繼承或者元素選擇符。

    <form class="Mtop(1point5) Bdc(#ccc) P(1point5)">
      <div class="Mtop(0)">
        <label for="one" class="Mtop(0) C(brandColor) Fs(bold)">Label one</label>
        <input id="one" name="one" type="text" class="Mtop(0point75) C(brandColor) Bdc(#fff) B(2) P(1)">
      </div>
      <div class="Mtop(1point5)">
        <label for="two" class="Mtop(0) C(brandColor) Fs(bold)">Label two</label>
        <input id="two" name="two" type="text" class="Mtop(0point75) C(brandColor) Bdc(#fff) B(2) P(1)">
      </div>
      <button type="submit" class="Mtop(1point5) C(#fff) Bdc(blue) P(1)">Submit</button>
    </form>

Atomic CSS 使開發者可以直接控制樣式而不再使用內聯樣式,內聯樣式不像樣式類一樣可以複用。通過為各種獨立的屬性提供樣式類,減少了樣式表中的重複宣告。

但是,它需要直接介入標記從而實現這些目的。這就要求學習並投入它那冗長的 API,同樣還需要編寫大量額外的 HTML 程式碼。

相反,如果只用來對任意 HTML 元素及其空間關係設計樣式的話,那麼 CSS “方法論”就要被大範圍棄用了。使用一致性設計的系統有著很大的優勢,相比一個疊加樣式的 HTML 系統更方便考慮和分開管理。

無論如何,下面是我們的 CSS 架構和流式佈局內容解決方案應該具備的特徵:

  1. 全域性(html)樣式並強制繼承,
  2. 流式佈局方法及部分例外(使用貓頭鷹選擇符),
  3. 元素及屬性樣式。

我們還沒有編寫一個特定元件或者構思一個 CSS 樣式類,但我們大部分的樣式都已經寫好了,前提是如果我們能夠將樣式類寫得合理且可複用。

工具類

關於樣式類它們有一個全域性作用域:在 HTML 裡任何地方使用,它們都會被關聯的 CSS 所影響。對大多數人來說,這都被看做一個弊端,因為兩個獨立的開發者有可能以同樣的命名來編寫一個樣式類,從而互相影響工作。

CSS modules 最近被用來解決這種情況,通過以程式來生成唯一的樣式類名,繫結到它們的區域性或元件作用域當中。

    <!-- my module's button -->
    <button class="button_dysuhe027653">Press me</button>

    <!-- their module's button -->
    <button class="button_hydsth971283">Hit me</button>

忽略掉生成程式碼的醜陋,你應該能夠看到兩個獨立元件之間的不同,並且可以輕易地放在一起:唯一的識別符號被用來區分同類的樣式。在這麼多更好的努力和冗餘程式碼下,結果介面將要麼不一致,要麼一致。

沒有理由對公共元素來進行唯一性區分。你應該對元素型別新增樣式,而不是元素例項。謹記 “class” 意味著“某種可能存在很多的東西的型別”。換句話說,所有的樣式類都應該是工具類:全域性可複用。

當然,在這個示例裡,總之 .button 類是冗餘的:我們可以用 button 元素選擇符來替代。但是如果有一種特殊型別的按鈕呢?比如,我們可能編寫一個 .danger 類來指明這個按鈕是做危險性操作,比如刪除資料:

    .danger {
      background: #c00;
      color: #fff;
    }

因為類選擇符的優先順序比元素選擇符的優先順序高,而和屬性選擇符優先順序相同,所以這種方式新增在樣式表後面的樣式規則會覆蓋前面元素和屬性選擇符的規則。所以,危險按鈕會以紅色背景配白色文字出現,但它其他的屬性,比如內邊距,聚焦輪廓以及外邊距都會通過之前的流式佈局方法新增,保持不變。

    <button class="danger">delete</button>

如果多位開發人員長時間在同樣的程式碼基礎上工作,那麼偶爾就會發生命名衝突。但是有幾種避免這種情況的方法,比如,噢,我不太知道,但對於你想要採用的名稱我建議首先做一個文字搜尋,看看是否已經存在了。因為你不知道,可能已經有人解決了你正在定位的問題。

區域性作用域的各種工具類

對於工具類來說,我最喜歡做的事情就是把它們設定在容器上,然後用這個鉤子去影響內部子元素的佈局。舉個例子,我可以快速對任意元素設定一個等間隔、響應式以及居中的佈局。

    .centered {
      text-align: center;
      margin-bottom: -1rem; /* adjusts for leftover bottom margin of children */
    }

    .centered > * {
      display: inline-block;
      margin: 0 0.5rem 1rem;
    }

使用這個方法,我可以把列表項、按鈕、按鈕組合以及連結等隨便什麼元素居中展示。全靠 > * 的使用,在這個作用域中,它意味著帶有 .centered 樣式的元素下最近的子元素將會採用這些樣式,並且還繼承全域性和父元素的樣式。

而且我調整了外邊距,好讓元素可以自由進行包裹,而且不會破壞使用 * + * 選擇符設定的垂直設定。這少量的程式碼通過對不同元素設定一個區域性作用域,就提供了一個通用、響應式的佈局解決方案。

我的一個小型(壓縮後 93B)的基於 flexbox 網格佈局系統 就是一個類似這種方法的工具類。它高度可複用,而且因為它使用了 flex-basis,所以不需要斷點干預。我只是用了 flexbox 佈局的方法。

    .fukol-grid {
      display: flex;
      flex-wrap: wrap;
      margin: -0.5em; /* adjusting for gutters */
    }

    .fukol-grid > * {
      flex: 1 0 5em; /* The 5em part is the basis (ideal width) */
      margin: 0.5em; /* Half the gutter value */
    }

使用 BEM 的方法,你會被鼓勵在每個網格項上放置一個明確的“元素”樣式類:

    <div class="fukol"> <!-- the outer container, needed for vertical rhythm -->
      <ul class="fukol-grid">
        <li class="fukol-grid__item"></li>
        <li class="fukol-grid__item"></li>
        <li class="fukol-grid__item"></li>
        <li class="fukol-grid__item"></li>
      </ul>
    </div>

但這不是必要的。只需一個識別符號去例項化本地作用域。這裡的列表項相比起我版本當中的列表項,不再受外部影響的保護,也不應該被 > * 所影響。僅有的區別就是充斥了大量樣式類的標記。

所以,現在我們已經開始合併樣式類,但只在通用性上合併,和它們所預期的效果一樣。我們仍然還沒有獨立地給複雜元件新增樣式。反而,我們在以一種可複用的方式解決一些系統性的問題。當然,你將需要在註釋裡寫清楚這些樣式類是如何使用的。

像這些的工具類同時採用了 CSS 的全域性作用域、區域性作用域、繼承以及層疊的優點。這些樣式類可以在各個地方使用,它們例項化區域性作用域從而隻影響它們的子元素,它們從父級或全域性作用域中繼承沒有設定在自身的樣式,而且我們沒有過度使用元素或類選擇符。

下面是現在我們的層疊看上去的樣子:

  1. 全域性(html)樣式和強制性繼承,
  2. 流式佈局方法和一些例外(使用貓頭鷹選擇符),
  3. 元素和屬性樣式,
  4. 通用的工具類。

當然,可能沒有必要去編寫所有這些示例工具類。重點是,如果在使用元件的時候出現了需求,那麼解決方案應該對所有元件都有效才行。一定要總是站在系統層面去思考。

特定元件樣式

我們從一開始就已經給元件新增了樣式,並且學習樣式結合元件的方法,所以很多人有可能會忽略掉馬上要講到這個部分。但值得說明的是,任何不是從其他元件中建立的元件(甚至包括單個 HTML 元素)都是有必要存在的。它們是使用 ID 選擇符的元件,以及有可能成為系統問題的風險。

事實上,一個好的實踐是隻使用 ID 來給複雜元件標識(“molecules”、“organisms”),並且不在 CSS 裡使用這些 ID。比如,你可以在登入表單元件上寫一個 #login,那麼你就不應該在 CSS 裡以元素、屬性或者流式佈局方法的樣式來使用 #login,即使你可能會發現你在創造一個或兩個可以在其他表單元件裡使用的通用工具類。

如果你確實使用了 #login,那麼它只會影響那個元件。值得提醒的是如果這麼做,那麼你就已經偏離了開發一個設計系統方向,並且朝著只有不停糾結畫素的冗長程式碼前進。

結論

當我告訴人們我不使用諸如 BEM 這樣的方法論或者 CSS 模組這樣的工具時,多數人會認為我會編寫下面這樣的 CSS:

    header nav ul li {
      display: inline-block;
    }

    header nav ul li a {
      background: #008;
    }

我沒有這樣做。一份清晰的陳述已經在這兒了,還有我們需要小心去避免的事情也已經闡述了。只是想說明 BEM(還有 OOCSS、SMACSS、atomic CSS 等)並不是避免複雜、不可能管理的 CSS 的唯一方法。

為了解決優先順序問題,許多方法論幾乎都選擇了使用類選擇符。問題在於這產生了大量的樣式類:讓 HTML 標記變得臃腫的各種神奇程式碼,以及失去了對文件的注意力,這些都會讓新來的開發者對他們所處的系統感到困擾和迷惑。

通過大量地使用樣式類,你還需要管理一個樣式系統,而且這個系統很大程度上是和 HTML 系統分離的。這種不太合適的所謂“關注點分離”可以造成冗餘,甚至更糟糕,導致不可訪問性:有可能會在可訪問的狀態下影響一個視覺上的樣式:

<input id="my-text" aria-invalid="false" class="text-input--invalid" />

為了替換掉大量的編寫和各種樣式類,我找到了其他一些方法:

  • 為了一致性掌握繼承去設定一個前置條件;
  • 充分使用元素和屬性選擇符去支援透明度和基於標準的組合樣式;
  • 使用簡便的的流式佈局系統;
  • 合併一些高度通用的工具類,解決影響多元素的共同佈局問題。

所有這些方法都是為了建立一個設計系統,使編寫一個新元件變得更簡單,以及當專案成熟的時候,減少新增新的 CSS 程式碼的依賴。並且這並不是獲益於嚴格的命名和合並,反而是因為缺少了它們。

可能你會對我在這裡推薦的特殊技巧並不感冒,但我還是希望這篇文章至少可以讓你重新思考一下元件是什麼。它們不是你獨立建立的東西。有的時候,在標準 HTML 元素的情況下,它們甚至不是你所建立的東西。你的元件從其他元件拿來的東西越多,那麼介面的可訪問性和視覺上的一致性就會變得更好,並且最後會用更少的 CSS 去實現它們。

(這些問題)CSS 並沒有太多過錯。事實上,讓你做很多事情是非常好的,我們只是沒有利用罷了。

相關文章