天天看點

CSS架構最佳實踐:預測、重用、擴充、維護

對于想踏入前端開發的工程師來說,通曉css(cascading style sheets)則是最基本的要求。而擅長css的web開發人員不僅可以從視覺上複制實物原型,還可以用代碼進行完美的呈現。無需使用表格、盡可能少的使用圖檔。如果你是個名副其實的高手,你可以快速把最新和最偉大的技術應用到你的項目中,比如媒體查詢、過渡、濾鏡、轉換等。雖然這些都是一個真正的css高手所具備的,但css很少被人單獨拿出來讨論,或者用它去評估某個人的技能。

有趣的是,我們很少這樣去評價其他語言。rails開發人員并不會因為其代碼比較規範,就認為他是一名優秀的開發人員。這僅僅是個基準。當然,他的代碼得必須規範。另外,還需集合其他方面考慮,比如代碼是否可讀?是否容易修改或擴充……

這都是些很自然的問題,css和它們并沒有什麼不同之處。今天的web應用程式要比以往更加龐大。一個缺乏深思熟慮的css架構往往會削弱發展,是時候像評估其他語言那樣,來評估一下css架構了,這些都不應該放在“事後”考慮或者單單屬于設計師們的事情。

CSS架構最佳實踐:預測、重用、擴充、維護

1.良好的css架構目标

好的css架構目标并不同于開發一個好的應用程式,它必須是可預測、可重用、可維護和可伸縮的。

可預測

可預測意味着可以像預期的那樣規範自己的行為。當你添加或者修改某個規則時,它并不會影響到沒有指定的部分。對于一個小網站來說,一些微乎其微的改變并不算什麼。而對于擁有成千上萬個頁面的大網站來說,可預測卻是必須的。

可重用

css規則應具備抽象和解耦性,這樣你就可以在現有的基礎上快速建構新的元件,無需重新修改編碼模式。

可維護

當把新元件放置到網站上,并且執行添加、修改或者重新設計操作時,無需重構現有css,并且新添加的x并不會打破原有頁面的y元件。

可擴充

當網站發展到一定規模後,都需要進行維護和擴充。可擴充的css意味着網站的css架構可以由個人或者團隊輕易地管理,無需花費太多的學習成本。

2.常見的錯誤實踐

在實作良好的css架構目标之前,我們來看一些常見的錯誤做法,這對我們達成目标是有好處的。

下面的這些例子雖然都可以很好的執行,但卻會給你帶來很多煩惱,盡管我們的意圖和願望都是美好的,但是這些開發模式會讓你頭疼。

幾乎在每個網站上,都會有一個特定的虛拟元素看起來與其他頁面是完全一樣的,然而隻有一個頁面除外。當面對這樣一種情況時,幾乎每個新手css開發人員(甚至是經驗豐富的)都會以同樣的方式來修改。你應該為該頁面找出些與衆不同之處(或者自己建立),然後再寫一個新規則去操作。

基于父元件來修改元件

.widget {  

  background: yellow;  

  border: 1px solid black;  

  color: black;  

  width: 50%;  

}  

#sidebar .widget {  

  width: 200px;  

body.homepage .widget {  

  background: white;  

初看,這絕對是段無害的代碼,但讓我們來看看它是否達到了我們所設定的目标。

首先,widget在examle是不可預見的。當這些小部件出現在頁面兩側或者首頁面時,開發人員期望它們以某種特定的方式顯示出來,且又不失特色。另外,它也是不可重用或不可擴充的。

另外,它也比較難維護。一旦這個widget需要重新設計,那麼你不得不修改其他幾個css樣式。想象一下,如果這段代碼是使用其他語言編寫的,它基本就是一個類定義,然後在代碼的另一部分使用該類定義并做出擴充。這直接違反了軟體開發的開放/閉合(open/close)原則。

軟體實體(類,子產品,函數等)應對擴充開放,對修改閉合。

過于複雜的選擇器

偶爾,會有些文章介紹css選擇器對整個網站的展示起着非常重要的作用,并且宣稱無需使用任何類選擇器或者id選擇器。

但伴随着越深入的開發,我越會遠離這種複雜的選擇器。一個選擇器越複雜,與html就越耦合。依靠html标簽群組合器可以保持html代碼幹幹淨淨,但卻讓css更加毛重和淩亂。

#main-nav ul li ul li div { }  

#content article h1:first-child { }  

#sidebar > div > h3 + p { } 

對上面代碼進行簡單的了解。第一個可能是對下拉菜單進行樣式化;第二個想說明文章的主标題應該與其他頁面的h1元素不同;最後一個表示在第一段的側邊欄區域添加一些額外的空間。

如果這個html是永遠不變的,那就無可說之處,但這根本毫不現實。過于複雜的選擇器會讓人印象深刻,它可以讓html擺脫掉表面上的複雜,但對于實作良好的css架構目标卻毫無用處。

上面提到的例子都是不具備可預測性、可重用、可擴充和可維護這四大特性的。例如第一個選擇器(下來菜單)例子,如果一個外觀非常相似的下拉清單需要用在不同的頁面上,并且#main-nav并不屬于内部元素,那麼你是否需要重新設計?假設開發者想要修改第三個例子裡div裡面部分标記,那麼整個規則都會被打破。

過于通用的類名

當建立可重用的設計元件時,在元件的類選擇器中覆寫附件的子元素是很常見的現象。例如:

<div class="widget">  

  <h3 class="title">...</h3>  

  <div class="contents">  

    lorem ipsum dolor sit amet, consectetur adipiscing elit.  

    in condimentum justo et est dapibus sit amet euismod ligula ornare.  

    vivamus elementum accumsan dignissim.  

    <button class="action">click me!</button>  

  </div>  

</div> 

.widget {}  

.widget .title {}  

.widget .contents {}  

.widget .action {} 

像.title、.contents、.action這些子元素類選擇器可以被安全地進行樣式命名,無需擔心這些樣式會蔓延到擁有相同類名的其他元素中。這是千真萬确的。但它并沒有阻止相同樣式類名稱會蔓延到這個元件上。

在一些大型項目上,像.title這樣的名稱很有可能會被用在另外一個頁面或者本身。如果這樣的情況發生,那麼整個标題部分明顯會和預期的不一樣。

過于通用的類選擇器名稱會導緻許多不可預測的css樣式發生。

一個規則做太多事

有時,你要在網站的左上角區域做一個20pixels的可視化元件。

  position: absolute;  

  top: 20px;  

  left: 20px;  

  background-color: red;  

  font-size: 1.5em;  

  text-transform: uppercase;  

下面,你需要在網站的其他區域使用該元件,那麼上面的這個代碼明顯是錯誤的,不可重用的。

問題的關鍵是你讓.widget這個選擇器做的事情太多,不僅對該元件的位置進行了規定,還對它的外觀和感覺方面進行了樣式。外觀和感覺可以通用,而位置是不可以的。有時候,把它們整合起來使用反而會大打折扣。

雖然這些看起來并無害處,對一些缺乏經驗的css程式員來說,複制和粘貼已經成為一種習慣。如果一個新團隊需要一個特定元件,比如.infobox,他們會嘗試使用這個類選擇器。但如果該資訊框沒有按照期望的那樣,在每個需要的地方正确顯示出來。這時,你認為他們會怎麼做?以我的經驗來看,他們會打破可重用這一規則,相反,他們會簡單地把這些代碼複制粘貼到每個需要的地方。做些不必要的重複工作。

3.原因

上面列舉的這些正常錯誤實踐都有一個相似性,css樣式承擔過多。

對這樣的說法你會感到奇怪,畢竟,它是一個樣式表,難道不應該承擔大多數(如果不是全部)的樣式嗎?那不正是我們想要的嗎?

的确。但是通常來講,事情并沒有那麼簡單。内容與表現(presentation)相分離是件好事,但css從html中獨立出來并不意味着内容也需要從表現中分離。換句話說,如果css請求深入分析html架構,那麼從html中分拆所有的顯示代碼并不一定會實作所有的目标。

此外,html很少會隻包含内容,也表示整體架構。通常,架構是會包含container元素,允許css隔離一些固定元素。即使沒有表象類(presentational classes),也能混合html清晰地把内容展示出來。

我相信,鑒于目前的html和css狀态,把html和css明智地結合起來,當做表現層是非常需要的。而通過模闆和局部模闆(partials)也可以把内容層進行分離。 

4.解決方案。

如果把html和css結合起來,作為一個web應用程式的表現層,那麼它們需要采取一些方式更好地促進優秀css架構的形成。

最好的方法是css中盡可能少的包含html架構。css則是應該定義元素的視覺效果,無論該視覺元素在哪裡。如果有一些特定的元件需要在不同的場合顯示不同的效果,那麼應該賦予不同的名稱。例如,css通過.button類選擇器定義了一個按鈕元件。如果html想要一個特定的元素看起來像按鈕,那麼就可以使用.button。如果這裡有特殊要求,這裡的按鈕與其他的有所不同(有可能更大和寬些),那麼css需要定義一個新的類,html可以使用新的類來賦予該元素新的視覺效果。

css賦予元素的外在特征,html在頁面上進行調用。更少的css能被更多的html架構調用是最好的。

準确地在html中聲明元素不僅可以清晰表達設計意圖,其他開發者也可以清晰地檢視标記并且知道元素将呈現的樣子。如果沒有這種實踐,它很難區分一個元素的外觀設定是有意或無意的,這樣很容易導緻團隊混亂。

在标記中填入大量的類(classes)是種常見缺陷,這樣做往往需要花費額外的精力。一個css樣式可以給一個特定元件引用上千次。那麼,為了在标記裡面進行顯示聲明,就真的值得去重複編寫這樣的類嗎?

雖然這種擔心是有效的,但它可能會産生誤導。言下之意就是無論你在css中使用一個父選擇器還是親手編寫上千個class,這裡都會有些額外的選擇。在rails或者其他架構裡檢視同級别抽象很大程度上可以在html中保持很好的視覺外觀,并且無需在類中一遍又一遍地編寫相同的類。

5.最佳實踐。

針對上面的種種錯誤,我進行了很好地總結,并且根據自身經驗提出了一些建議,希望它們能幫助您更好地實作良好的css架構目标。

專注

確定選擇器對一些元素不進行無關樣式的最好方法是不給它們機會。例如像#main-nav ul li ul li div這樣的選擇器可能很容易地應用于不想要的元素上。另一方面,像.subnav這樣的選擇器就不會給它們任何機會。把類選擇器直接應用于你想要的元素上是最好的方式,并且可以保持元素的可預測性。

/* grenade */ 

#main-nav ul li ul { }  

/* sniper rifle */ 

.subnav { } 

子產品化

一個組織結構良好的元件層可以幫助解決html架構與css那種松散的耦合性。此外,css元件本身應該是子產品化的。元件應該知道如何進行樣式和更好地工作,但是關于布局、定位以及它們與周圍元素的關系不應該做太多的假設。

一般而言,css要定義的應該是元件的外觀,而不是布局或者位置。同樣在使用background、color和font這些屬性時也要遵循原則使用。

布局和位置應當由一個單獨的布局類或者單獨的容器元素構成(請記住,有效地把内容與展示進行分離其實就是把内容與容器進行分離)。

給類進行命名空間

我們已經檢查出為什麼父選擇器不能在封閉和防止交叉樣式污染上面發揮100%的功效。而一個更好的解決方案就是在類上應用命名空間。如果一個元素是可視化元件的一員,那麼該元素的每個子元素都應該使用基于命名空間的元件。

/* high risk of style cross-contamination */ 

.widget { }  

.widget .title { }  

/* low risk of style cross-contamination */ 

.widget-title { } 

給類進行命名空間可以保持元件獨立性和子產品化。它可以把現有類沖突降至最小并且減少子元素的一些特殊要求。

建立修飾符類來擴充元件

當一個現有元件需要在一個特定的語境中有所不同時,可以建立一個修飾符類(modifier class)來擴充它。

/* bad */ 

#sidebar .widget { }  

/* good */ 

.widget-sidebar { } 

正如我們看到的,基于父元素的缺點對元件進行修改,需要重申:一個修飾符類可以在任何地方使用。基于位置的覆寫隻能被用在一個特定的位置,修飾符類也可以根據需要被多次使用。顯然,修飾符類是符合html開發者需求的。

把css組織成邏輯結構

元件是一個獨立的視覺元素。模闆在另一方面則是建構塊。模闆很少獨自站在自己的角度去描述視覺和感覺,相反,它們是單一的、可重用的模式,可以放在一起形成元件。

為了提供更詳細的例子,一個元件可能就是一個模式對話框。該模式可能在頭部包含漸變的網站簽名、或者在周圍會有陰影、在右上角會有關閉按鈕、位置固定在垂直與水準線中間。這四個模式可能被網站重複多次使用,是以在每次使用的時候,你都很少會想到重新編碼與設計。這些所有的模闆即形成了一個子產品元件。

因樣式和風格使用類

有過大型網站建設的人可能有個這樣的經驗,一個擁有類的html元素可能完全不知道其用途。你想删除它,但是又猶豫不決,因為它的作用你可能還未意識到。一旦這樣的事情一遍又一遍發生的時候,随着時間的推移,項目中将會有越來越多這樣的類,隻因為團隊成員都不敢删除。

在web前端開發中,類承擔了太多的責任,是以才會産生這樣的問題。樣式化html元素、扮演着javascript hook角色、功能檢測、自動化測試等。當這麼多應用程式在使用類時,讓你從html中删除它們将會變的非常艱難。

然而,使用一些成熟的約定(慣例)即可完全避免這種問題。當在html中看到一個類時,你應該立即明白它的目的。我建議在前面使用字首,例如用于javascript的在前面加.js,表示modernizr classes可以在前面加.supports,沒有加字首的即用于表示樣式。

這樣來發現未使用的類和從html中移除它們将會變得非常簡單。你甚至可以自動完成這一個過程,在javascript中通過交叉引用html中的document.stylesheets對象。如果在document.stylesheets中沒有發現該類,即可安全移除。

一般來說,最佳做法是把内容與示範相分離,另外把功能分離開來也同樣重要。使用樣式類像javascript hook在某種程度上可以加深css與javascript之間的耦合,但在不打破功能性的前提下很難或者根本不可能更改外觀。 

有邏輯的命名類

大多數寫css的人喜歡使用連字元來分隔命名詞,但連字元并不足以區分不同類型之間的類。

/* a component */ 

.button-group { }  

/* a component modifier (modifying .button) */ 

.button-primary { }  

/* a component sub-object (lives within .button) */ 

.button-icon { }  

/* is this a component class or a layout class? */ 

.header { } 

從上述類中可以發現其很難正确區分類型規則。這不但會困惑,而且連自動測試css和html也變的很難。一個結構化的命名約定應該是初看就能夠知道其類名與其他類之間的關系,并且知道它出現在html中的位置——使命名更加簡單和容易測試。

/* templates rules (using sass placeholders) */ 

%template-name  

%template-name--modifier-name  

%template-name__sub-object  

%template-name__sub-object--modifier-name  

/* component rules */ 

.component-name  

.component-name--modifier-name  

.component-name__sub-object  

.component-name__sub-object--modifier-name  

/* layout rules */ 

.l-layout-method  

.grid  

/* state rules */ 

.is-state-type  

/* non-styled javascript hooks */ 

.js-action-name 

重做第一個例子:

.button--primary { }  

.button__icon { }  

/* a layout class */ 

.l-header { } 

6.工具

維護一個高效且組織良好的css架構是非常困難的,尤其是在大型團隊中。下面向大家推薦幾款很好的工具來幫你管理網站css架構。

css preprocessor

css預處理器采用php5編寫,有預處理器的常見功能,可以幫你快速編寫css。另外有些号稱“功能”的預處理器實際上并不會對css架構産生良好作用。下面我提供一個清單,在使用時一定要避免:

切勿純粹為了組織代碼來嵌套規則。隻有當輸出你真正想要的css時才可以。

在無需傳遞參數的時候切勿使用mixin,不帶參數的mixin更适合用作模闆,易擴充。

切勿在選擇器上使用@extend,它不是個單一的類。從設計角度來看是毫無意義的,它會膨脹編譯過的css。

在運用元件修飾符規則時,切勿使用@extend ui元件,這樣會失去基礎鍊。

當你初次使用@extend時,常會與修飾符類一起使用,例如:

.button {  

  /* button styles */ 

.button--primary {  

  @extend .button;  

  /* modification styles */ 

這樣做會讓你在html中失去繼承鍊。很難使用javascript選擇所有的按鈕執行個體。

作為一般規則,很少去擴充ui元件或者在知道類型後做些什麼。這是區分模闆群組件的一種方式,模闆無需參與到應用程式的邏輯,并且可以使用預處理器進行安全擴充。

下面是一個引用上面的模式例子:

.modal {  

  @extend %dialog;  

  @extend %drop-shadow;  

  @extend %statically-centered;  

  /* other modal styles */ 

.modal__close {  

  @extend %dialog__close;  

  /* other close button styles */ 

.modal__header {  

  @extend %background-gradient;  

  /* other modal header styles */ 

css lint

css lint是一個用來幫你找出css代碼中問題的工具,它可做基本的文法檢查以及使用一套預設的規則來檢查代碼中的問題,規則是可以擴充的。

使用css lint建議:

不要在選擇器中出現id。

在多部分規則中,不要使用非語義(non-semantic)類型選擇器,例如div、span等。

在一個選擇器中使用的連接配接符(combinator)不要超過2個。

任何類名都不要以“js-”開始。

如果在非“i-”字首規則裡經常使用布局和定位應給予警告

如果一個類定義後被重新定義成子類,也應給予警告。

總結

css不僅僅是視覺設計,也不要因為你編寫css就随便抛出程式設計的最佳實踐。像oop、dry、打開/閉合、與内容分離等這些規則應該應用到css裡面。無論你如何組織代碼,都要確定方法真正幫助到你,并且使你的開發更加容易和可維護的。

繼續閱讀