如何寫一份不錯的 CSS 代碼?

背景介紹:當我們在從事大項目或團隊開發工作時,我們經常會發現我們寫的代碼,凌亂、難以閱讀並且難以擴展。尤其是當一段時候後我們回頭再看自己的代碼,必須回想起當初自己寫的時候的思路才能看懂。 

因此,人們嘗試在代碼風格上保持統一,然而,最大的困難是:修改一個較小的問題,都可能創建更多醜陋的 hack,也可能 CSS 的小改變會影響 JavaScript 的功能。但是這些問題能在我們的項目開始的時候精心規劃,就能很大程度上避免這些問題。今天就來討論一下如何寫一份不錯的 CSS 代碼

一個好的 css 代碼是什麼樣的呢

要保持良好的 CSS 代碼,首先需要訂立一致的 CSS 團隊規範,這就必須從 CSS 架構講起。

CSS 架構

目前 CSS 主要有以下五種設計架構

1. OOCSS

面向對象的 CSS,

在 OOCSS 的基礎上,出現了另一種設計模式

2. BEM

也可以被當成一種命名規範,本質上使頁面結構清晰

塊(Block)、元素(Element__)、修飾符(Modifier--)

有以下幾個規則

可以明顯發現

在面對組件化的場景時,Block 代表邏輯上和功能上獨立的頁面組件。Element 封裝了行爲 (JavaScript)、模板、樣式(CSS) 和其他實現技術。

舉個例子

<header class="header">
    <img class="logo">
    <form class="search-form">
        <input class="input">
        <button class="button"></button>
    </form>
    <ul class="lang-switcher">
        <li class="lang-switcher__item">
            <a class="lang-switcher__link" href="url">en</a>
        </li>
        <li class="lang-switcher__item">
            <a class="lang-switcher__link--active" href="url">ru</a>
        </li>
    </ul>
</header>

在 React 當中,也採用了這樣的命名方式

BEMnaming 工具 [1],提供 BEM 命名的檢測

然而在面對大型的項目時 CSS 的凌亂也很難讓開發者願意在茫茫多的代碼中尋找可複用的代碼

3. SMACSS

(What’s Smacss)[https://smacss.com/]

設計的主要規範有三點:

爲了實現清晰的 CSS 結構,將 CSS 分爲

其中尤其建議 JavaScript 解除和樣式的耦合

<button class="btn btn--buy js-buy-now"></button>

命名規範上出現了一些差異

.layout-header
.is-hidden
.theme-nav

最小化適配深度,減少 html 和 css 的耦合度,避免 html 的變動增加對 css 的影響

.sidebar ul h3 {}


.side {}

4. ITCSS

對 CSS 進行了更加詳細的分層

如果從功能的角度上看呢,是將 Base 分成了 Settings、Tools、Generic 和 Base,而 Objects、Components 和 Trumps 則分別對應 Layout、Module、(State、Theme),而這樣設計的好處在於可以將代碼的複用性進一步提升

5. ACSS

一個樣式屬性一個類,其中的典型代表就是 TailwindCSS[2],缺點則是破壞了語義化

.block{ display: block; }
.hidden { display: none; }
.p-2 { padding: 0.75rem; }
.flex { display: flex; }
.text-base { font-size: 1rem; }
.bg-green-200 { background-color: #123456 }


<div class>I am Ok</div>

而上述的架構思想,更多則是需要團隊成員的一致性認同,才能實現在代碼風格上的統一。

除了這些開發自律性上的代碼規範外,還有什麼其他的方式來提升 CSS 質量呢?

CSS 預處理器

在預處理器中,同樣提供了衆多的方法來簡化與控制 CSS 代碼,以 stylus 爲例

1. 變量

font-size = 14px


 body
   font font-size Arial, sans-serif
   
pad(types = padding, n = 5px)
    if padding in types
      padding n
    if margin in types
      margin n


body
    pad()
body
    pad(margin)
body
    pad(padding margin, 10px)
    
// Yields:
body {
    padding: 5px;
}
body {
    margin: 5px;
}
body {
    padding: 10px;
    margin: 10px;
}

2. 函數

 add(a, b = a)
   a + b


 add(10, 5)
 // =15
 
  get(hash, key)
    return pair[1] if pair[0] == key for pair in hash
    
  hash = (one 1) (two 2) (three 3)
  
  get(hash, two)
  // =2


  get(hash, three)
  // =3


  get(hash, something)
  // => null

3. 內建函數

// 提取顏色分量
red(#c00)
// =204


red(#000, 255)
// =#f00

4. 插值

// 屬性插值
vendor(prop, args)
    -webkit-{prop} args
    -moz-{prop} args
    {prop} args


border-radius()
    vendor('border-radius', arguments)


box-shadow()
    vendor('box-shadow', arguments)


button
    border-radius 1px 2px / 3px 4px


    
// Yields:    


button {
    -webkit-border-radius: 1px 2px / 3px 4px;
    -moz-border-radius: 1px 2px / 3px 4px;
    border-radius: 1px 2px / 3px 4px;
  }
  
// 選擇器插值  
table
  for row in 1 2 3 4 5
    tr:nth-child({row})
      height: 10px * row
      
      
// Yields:  
  
table tr:nth-child(1) {
  height: 10px;
}
table tr:nth-child(2) {
  height: 20px;
}
table tr:nth-child(3) {
  height: 30px;
}
table tr:nth-child(4) {
  height: 40px;
}
table tr:nth-child(5) {
  height: 50px;
}    


mySelectors = '#foo,#bar,.baz'


{mySelectors}
  background: #000
  
  
Yields:


#foo,
#bar,
.baz {
  background: #000;
}  


// 對象插值
foo = {
  width: 10px,
  height: 20px,
  '&:hover'{
    padding: 0


  }
}


.bar
  {foo}




Yields:


// => .bar {
//      width: 10px;
//      height: 20px;
//    }
//    .bar:hover {
//      padding: 0;
//    }

5. @EXTEND

   form
     input[type=text]
       padding: 5px
       border: 1px solid #eee
       color: #ddd


   textarea
     @extends form input[type=text]
     padding: 10px
     
     
    //Yielding:
    
    form input[type=text],
    textarea {
      padding: 5px;
      border: 1px solid #eee;
      color: #ddd;
    }
    textarea {
      padding: 10px;
    }

對於維護一份高質量的 CSS 代碼,註釋和間隔必不可少

以下是一種比較建議的註釋和間隔方式,可以自行取用。

/*------------------------------------*\
  #A-SECTION
*------------------------------------*/


.selector { }




/*------------------------------------*\
  #ANOTHER-SECTION
*------------------------------------*/


/**
 * Comment
 */


.another-selector { }

除了縮進,我們還可以通過在規則集之間自由而明智地使用空格來提供大量信息。我們用:

// good case
/*------------------------------------*\
  #FOO
*------------------------------------*/


.foo { }


  .foo__bar { }




.foo--baz { }


// bad case
.foo { }
  .foo__bar { }
.foo--baz { }

同理,在 html 結構中,也可以使用同樣的規則。

除了以上這些,還有衆多的規範和優化可以繼續探索,如選擇器性能,CSS 嵌套,有興趣的讀者可以繼續探索

你會認爲 CSS 規範是一個有點宏大和不必要的概念:爲什麼這麼簡單、這麼直接的東西需要像架構一樣被設計成非常複雜的東西?!

正是因爲 CSS 的簡單性、鬆散性和不守規矩的性質意味着在任何合理規模上管理(閱讀、馴服)它的最佳方式是通過嚴格和特定的架構。堅實的架構可以幫助我們控制我們的特殊性,強制執行命名約定,管理我們的源代碼順序,創建一個健全的開發環境,並且通常使我們的 CSS 項目管理更加一致和舒適。

總的來說,可以依照一下幾個規則訂立團隊 / 個人代碼規範,保證代碼的一致性

建議的幾個原則

單一職責原則: 每個 CSS 實現都必須有一個單一的責任。

Correct:
    .button {
        font-family: Arial, sans-serif;
        border: 1px solid black;
        background: #fff;
    }
    .header__button {
        margin: 30px;
        position: relative;
    }
    
Incorrect:
    .header__button {
        font-family: Arial, sans-serif;
        position: relative;
        border: 1px solid black;
        margin: 30px;
    }

開閉原則: 元素應該通過修飾符擴展,而不是直接在原有基礎上修改。

Original:
    <button class="button">...</button>
    .button {
        font-family: Arial, sans-serif;
        text-align: center;
        font-size: 11px;
        line-height: 20px;
    }
    
Extend
    <button class="button button_size_s">...</button>
    .button {
        font-family: Arial, sans-serif;
        text-align: center;
        font-size: 11px;
        line-height: 20px;
    }
    .button_size_s {
        font-size: 13px;
        line-height: 24px;
    }

DRY 原則:將有意義的重複規範化和抽象化

巧用mixin和extend
@mixin my-web-font() {
  font-family: "My Web Font", sans-serif;
  font-weight: bold;
}


.btn {
  display: inline-block;
  padding: 1em 2em;
  @include my-web-font();
}


.foo {
  color: red;
}
.bar {
  @extend .foo;
}

組合優於繼承和關注點分離

// 將寫js的方式同樣適用在css上
<div class="layout">


  <div class="layout__item  two-thirds">
    <section class="content">
      ...
    </section>
  </div>


  <div class="layout__item  one-third">
    <section class="sub-content">
      ...
    </section>
  </div>


</div>

參考文獻

BEM 簡介 [3]

OOCSS 介紹 [4]

探索 SMACSS:可擴展的模塊化 CSS 框架 [5]

編寫高效的 CSS 選擇器 [6]

CSS 的單一原則 [7]

思考 CSS 架構 [8]

參考資料

[1] 

BEMnaming 工具: https://github.com/bem/bem-sdk#naming

[2] 

TailwindCSS: https://www.tailwindcss.cn/

[3] 

BEM 簡介: https://en.bem.info/methodology/quick-start/

[4] 

OOCSS 介紹: http://oocss.org/

[5] 

探索 SMACSS:可擴展的模塊化 CSS 框架: https://zhuanlan.zhihu.com/p/44851489

[6] 

編寫高效的 CSS 選擇器: https://csswizardry.com/2011/09/writing-efficient-css-selectors/

[7] 

CSS 的單一原則: https://csswizardry.com/2012/04/the-single-responsibility-principle-applied-to-css/

[8] 

思考 CSS 架構: https://zhuanlan.zhihu.com/p/32952130

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/XlULcjzJbk9t0QpRIIq1Ag