在參與規模龐大、歷時漫長且人手衆多的項目時,全部開發者遵照以下規則極爲重要:css
+ **保持 CSS 的可維護性**html
+ **保持代碼清晰易懂**前端
+ **保持代碼的可拓展性**git
爲了實現這一目標,咱們要採用諸多方法。github
本文檔第一部分將探討語法、格式以及 CSS 分析;第二部分將從方法論、思惟框架以及編寫與規劃 CSS 的態度入手。web
## 目錄算法
* CSS 文檔分析設計模式
* 總覽瀏覽器
* 單一文件與多文件app
* 目錄
* 章節標題
* 代碼順序
* 規則解析
* 命名規範
* JavaScript 鉤子
* I18n
* 註釋
* 註釋的拓展用法
* 準修飾選擇器
* 代碼標籤
* 繼承標記
* 編寫 CSS
* 添加新部分
* 面向對象 CSS
* 佈局
* 界面尺寸
* 字號調節
* 簡寫
* IDs
* 選擇器
* 過修飾選擇器
* 選擇器性能
* 選擇器繼承
* `!important`
* 魔數與絕對比例
* 條件判斷
* Debugging
* 預處理
---
## CSS 文檔分析
不管編寫什麼文檔,咱們都應當盡力維持統一的風格,包括統一的註釋、統一的語法與統一的命名規範。
### 總則
儘可能將行寬控制在 80 字節如下。漸變(gradient)相關的語法以及註釋中的 URL 等能夠算做例外,畢竟這部分咱們也無能爲力。
我傾向於用 4 個空格而非 Tab 縮進,而且將聲明拆分紅多行。
### 單一文件與多文件
有些人喜歡將樣式寫成一個大文件,這並不賴,並且若是你按照下文的規則來編寫的話也不會遇到什麼問題。我在遷移至 Sass 以後,開始將樣式拆分紅衆多小文件。這也不賴。不管你採用什麼方式,下文中的規則都將適用。這兩種寫法僅僅在目錄以及區塊標題上有所差別。
### 目錄
在 CSS 的開頭,我會寫一份目錄,例如:
/*------------------------------------*\
$CONTENTS
\*------------------------------------*/
/**
* CONTENTS............You’re reading it!
* RESET...............Set our reset defaults
* FONT-FACE...........Import brand font files
*/
這份目錄能夠告訴其餘開發者這個文件中具體含有哪些內容。這份目錄中的每一項都與其對應的區塊標題相同。
若是你在維護一份規模較大的單文件 CSS,對應的區塊將也在同一文件中。若是你是在編寫一組小文件,那麼目錄中的每一項應當對應相應的 @include 語句。
### 區塊標題
目錄應當對應區塊的標題。請看以下示例:
/*------------------------------------*\
$RESET
\*------------------------------------*/
區塊標題前綴 `$` 可讓咱們使用([Cmd|Ctrl]+F)命令查找 `$[SECTION-NAME]`,同時 **將搜索範圍限制在區塊標題中**。
若是你在維護一份大文件,那麼在區塊之間空 5 行,以下:
/*------------------------------------*\
$RESET
\*------------------------------------*/
[Our
reset
styles]
/*------------------------------------*\
$FONT-FACE
\*------------------------------------*/
在大文件中快速翻動時這些大塊的空檔有助於區分區塊。
若是你在維護多份、以 @include 鏈接的 CSS 的話,在每份文件頭加上標題便可,沒必要這樣空行。
## 順序
儘可能按照特定順序編寫規則,這將確保你充分發揮 CSS 縮寫中第一個 <i>C</i> 的意義:cascade,層疊。
一份規劃良好的 CSS 應當按照以下排列:
1. **Reset** 萬物之根源
2. **元素類型** 沒有設置 class 的 `h1`、`ul` 等
3. **對象以及抽象內容** 最通常、最基礎的設計模式
4. **子元素** 由對象延伸出來的全部拓展及其子元素
5. **修補** 針對異常狀態
如此一來,當你依次編寫 CSS 時,每一個區塊均可以自動繼承在它以前區塊的屬性。這樣就能夠減小代碼相互抵消的部分,減小某些特殊的問題,構成設計更理想的 CSS 結構。
關於這方面的更多信息,強烈推薦 Jonathan Snook 的 [SMACSS](http://smacss.com)。
## CSS 規則集分析
[selector]{
[property]:[value];
[<- Declaration ->]
}
[選擇器]{
[屬性]:[值];
[<- 聲明 ->]
}
編寫 CSS 樣式時,我習慣遵照這些規則:
* class 名稱以連字符(-)鏈接,除了下文提到的 BEM 命名法;
* 縮進 4 空格;
* 聲明拆分紅多行;
* 聲明以相關性順序排列,而非字母順序;
* 有前綴的聲明適當縮進,對齊其值;
* 縮進樣式從而反映 DOM;
* 保留最後一條聲明結尾的分號。
例如:
.widget{
padding:10px;
border:1px solid #BADA55;
-webkit-border-radius:4px;
-moz-border-radius:4px;
border-radius:4px;
}
.widget-heading{
font-size:1.5rem;
line-height:1;
font-weight:bold;
color:#BADA55;
margin-right:-10px;
margin-left: -10px;
padding:0.25em;
}
咱們能夠發現,`.widget-heading` 是 `.widget` 的子元素,由於前者比後者多縮進了一級。這使得開發者在閱讀這些樣式時能夠快速獲取信息。
咱們還能夠發現 `.widget-heading` 的聲明是根據其相關性排列的:`.widget-heading` 是文字元素,因此咱們先添加字體相關的樣式聲明,接下來是其它的。
如下是一個沒有拆分紅多行的例子:
.t10 { width:10% }
.t20 { width:20% }
.t25 { width:25% } /* 1/4 */
.t30 { width:30% }
.t33 { width:33.333% } /* 1/3 */
.t40 { width:40% }
.t50 { width:50% } /* 1/2 */
.t60 { width:60% }
.t66 { width:66.666% } /* 2/3 */
.t70 { width:70% }
.t75 { width:75% } /* 3/4*/
.t80 { width:80% }
.t90 { width:90% }
在這個例子(來自[inuit.css’s table grid system](https://github.com/csswizardry/inuit.css/blob/master/inuit.css/partials/base/_tables.scss#L88))中,將 CSS 放在一行內可使得代碼更緊湊。
## 命名規範
通常狀況下我都是以連字符(-)鏈接 class 的名字(例如 `.foo-bar` 而非 `.foo_bar` 或 `.fooBar`),不過在某些特定的時候我會用 BEM(Block, Element, Modifier)命名法。
<abbr title="Block, Element, Modifier">BEM</abbr> 命名法可使得選擇器更規範,更清晰,更具語義。
該命名法按照以下格式:
.block{}
.block__element{}
.block--modifier{}
其中:
* `.block` 表明某個基本的抽象元素;
* `.block__element` 表明 `.block` 這一總體的一個子元素;
* `.block--modifier` 表明 `.block` 的某個不一樣狀態。
打個比方:
.person{}
.person--woman{}
.person__hand{}
.person__hand--left{}
.person__hand--right{}
這個例子中咱們描述的最基本元素是一我的,而後這我的多是一個女人。咱們還知道人擁有手,這些是人體的一部分,而手也有不一樣的狀態,如同左手與右手。
這樣咱們就能夠根據親元素來規定選擇器的命名空間並傳達該選擇器的職能,它是一個子元素(`__`)仍是不一樣狀態(`--`)?
由此,`.page-wrapper` 是一個獨立的選擇器。這是一個符合規範的命名,由於它不是其它元素的子元素或其它狀態;然而 `.widget-heading` 則與其它對象有關聯,它應當是 `.widget` 的子元素,因此咱們應當將其重命名爲 `.widget__heading`。
BEM 命名法雖然不太好看,並且至關冗長,可是它使得咱們能夠經過名稱快速獲知元素的功能和元素之間的關係。與此同時,BEM 語法中的重複部分很是有利於 gzip 的壓縮算法。
不管你是否使用 BEM 命名法,你都應當確保 class 命名得當,力保一字很少、一字很多;將元素命名抽象化以提升複用性(例如 `.ui-list`,`.media`)。由此延伸出去的元素命名則要儘可能精準(例如 `.user-avatar-link`)。不用擔憂 class 名的數量或長度,由於寫得好的代碼 gzip 也能有效壓縮。
### HTML 中的 class
爲了確保易讀性,在 HTML 標記中用兩個空格隔開 class 名,例如:
<div class="foo--bar bar__baz">
增長的空格應當可使得在使用多個 class 時更易閱讀與定位。
### JS 鉤子
**千萬不要把 CSS 樣式用做 JavaScript 鉤子。**把 JS 行爲與樣式混在一塊兒將沒法對其分別處理。
若是你要把 JS 和某些標記綁定起來的話,寫一個 JS 專用的 class。簡單地說就是劃定一個前綴 `.js-` 的命名空間,例如 `.js-toggle`,`.js-drag-and-drop`。這意味着咱們能夠經過 class 同時綁定 JS 和 CSS 而不會由於衝突而引起麻煩。
<th class="is-sortable js-is-sortable">
</th>
上面的這個標記有兩個 class,你能夠用其中一個來給這個可排序的表格欄添加樣式,用另外一個添加排序功能。
### I18n
雖然我(該 CSS Guideline 文檔原做者 Harry Roberts)是個英國人,並且我一貫拼寫 <i>colour</i> 而非 <i>color</i>,可是爲了統一性,我認爲在 CSS 中用美式拼法更佳。CSS 以及其它多數語言都是以美式拼法編寫,因此若是在 `.colour-picker{}` 中寫 `color:red` 就缺少統一性。我之前主張同時用兩種拼法,例如:
.color-picker,
.colour-picker{
}
可是我最近參與了一份規模龐大的 Sass 項目,這個項目中有許多的顏色變量(例如 `$brand-color`,`$highlight-color` 等等),每一個變量要維護兩種拼法實在辛苦,要查找並替換時也須要兩倍的工做量。
因此爲了統一性,把全部的 class 與變量都以你參與的項目的慣用拼法命名便可。
## 註釋
我使用行寬不超過 80 字節的塊狀註釋:
/**
* This is a docBlock style comment
*
* This is a longer description of the comment, describing the code in more
* detail. We limit these lines to a maximum of 80 characters in length.
*
* We can have markup in the comments, and are encouraged to do so:
*
<div class="foo">
<p>Lorem</p>
</div>
*
* We do not prefix lines of code with an asterisk as to do so would inhibit
* copy and paste.
*/
/**
* 這是一個文檔塊(DocBlock)風格的註釋。
*
* 這裏開始是描述更詳細、篇幅更長的註釋正文。固然,咱們要把行寬控制在 80 字之內。
*
* 咱們能夠在註釋中嵌入 HTML 標記,並且這也是個不錯的辦法:
*
<div class="foo">
<p>Lorem</p>
</div>
*
* 若是是註釋內嵌的標記的話,在它前面不加星號,不然會被複制進去。
*/
在註釋中應當儘可能詳細描述代碼,由於對你來講清晰易懂的內容對其餘人可能並不是如此。每寫一部分代碼就要專門寫註釋以詳解。
### 註釋的拓展用法
註釋有許多很先進的用法,例如:
* 準修飾選擇器
* 代碼標籤
* 繼承標記
#### 準修飾選擇器
你應當避免過度修飾選擇器,例如若是你能寫 `.nav{}` 就儘可能不要寫 `ul.nav{}`。過度修飾選擇器將影響性能,影響 class 複用性,增長選擇器私有度。這些都是你應當竭力避免的。
不過有時你可能但願告訴其餘開發者 class 的使用範圍。以 `.product-page` 爲例,這個 class 看起來像是一個根容器,多是 `html` 或者 `body` 元素,可是僅憑 `.product-page` 則沒法判斷。
咱們能夠在選擇器前加上準修飾(即將前面的類型選擇器註釋掉)來描述咱們規劃的 class 做用範圍:
/*html*/.product-page{}
這樣咱們就能準確獲知該 class 的做用範圍而不會影響複用性。
其它例子如:
/*ol*/.breadcrumb{}
/*p*/.intro{}
/*ul*/.image-thumbs{}
這樣咱們就能在不影響代碼私有度的前提下獲知 class 做用範圍。
#### 代碼標籤
若是你寫了一個新規則的話,能夠在它上面加上標籤,例如:
/**
* ^navigation ^lists
*/
.nav{}
/**
* ^grids ^lists ^tables
*/
.matrix{}
這些標籤可使得其餘開發者快速找到相關代碼。若是一個開發者須要查找和列表相關的部分,他只要搜索 `^lists` 就能快速定位到 `.nav`,`.matrix` 以及其它相關部分。
#### 繼承標記
將面向對象的思路用於 CSS 編寫的話,你常常能找到兩部分 CSS 密切相關(其一爲基礎,其一爲拓展)卻分列兩處。咱們能夠用繼承標記來在原元素和繼承元素之間創建緊密聯繫。這些在註釋中的寫法以下:
在元素的基本樣式中:
/**
* Extend `.foo` in theme.css
*/
.foo{}
在元素的拓展樣式中:
/**
* Extends `.foo` in base.css
*/
.bar{}
這樣一來咱們就能在兩塊相隔很遠的代碼間創建緊密聯繫。
---
## 編寫 CSS
以前的章節主要探討如何規劃 CSS,這些都是易於量化的規則。本章將探討更理論化的東西,也將探討咱們的態度與方法。
## 編寫新組件
編寫新組件時,要在着手處理 CSS **以前** 寫好 HTML 部分。這能夠令你準確判斷哪些 CSS 屬性能夠繼承,避免重複浪費。
先寫標記的話,你就能夠關注數據、內容與語義,在這以後再添加須要的 class 和 CSS 樣式。
## 面向對象 CSS
我以面向對象 CSS 的方式寫代碼。我把組件分紅結構(對象)與外觀(拓展)。正如如下分析(注意這個只是筆記而非例子):
.room{}
.room--kitchen{}
.room--bedroom{}
.room--bathroom{}
咱們在屋子裏有許多房間,它們都有共同的特色:它們都包含地板、天花板、牆壁和門。這些共享的部分咱們能夠放到一個抽象的 `.room{}` class 中。不過咱們還有其它不同凡響的房間:一個廚房可能有地磚,臥室可能有地毯,洗手間可能沒有窗戶可是臥室會有,每一個房間的牆壁顏色也許也會不同。面向對象 CSS 的思路使得咱們把相同部分抽象出來組成結構部分,而後用更具體的 class 來拓展這些特徵並添加特殊的處理方法。
因此比起編寫大量的特殊模塊,應當努力找出這些模塊中重複的設計模式並將其抽象出來,寫成一個能夠複用的 class,將其用做基礎而後編寫其它拓展模塊的特殊情形。
當你要編寫一個新組件時,將其拆分紅結構和外觀。編寫結構部分時用最通用 class 以保證複用性,編寫外觀時用更具體的 class 來添加設計方法。
## 佈局
全部組件都不要聲明寬度,而由其親元素或格柵系統來決定。
**堅定不要** 聲明高度。高度應當僅僅用於尺寸已經固定的東西,例如圖片和 CSS Sprite。在 `p`,`ul`,`div` 等元素上不該當聲明高度。若是須要的話能夠寫 `line-height`,這個更加靈活。
格柵系統應當看成書架來理解。是它們容納內容,而不是把它們自己當成內容裝起來,正如你先搭起書架再把東西放進去。比起聲明它們的尺寸,把格柵系統和元素的其它屬性分來開處理更有助於佈局,也使得咱們的前端工做更高效。
你在格柵系統上不該當添加任何樣式,他們僅僅是爲佈局而用。在格柵系統內部再添加樣式。在格柵系統中任何狀況下都不要添加盒模型相關屬性。
## UI 尺寸
我用不少方法設定 UI 尺寸,包括百分比,`px`,`em`,`rem` 以及乾脆什麼都不用。
理想狀況下,格柵系統應當用百分比設定。如上所述,由於我用格柵系統來固定欄寬和頁寬,因此我能夠不用理會元素的尺寸。
我用 rem 定義字號,而且輔以 px 以兼容舊瀏覽器。這能夠兼具 em 和 px 的優點。下面是一個很是漂亮的 Sass Mixin,假設你在別處聲明瞭基本字號(base-font-size)的話,用它就能夠生成 rem 以及兼容舊瀏覽器的 px。
@mixin font-size($font-size){
font-size:$font-size +px;
font-size:$font-size / $base-font-size +rem;
}
我只在已經固定尺寸的元素上使用 px,包括圖片以及尺寸已經用 px 固定的 CSS Sprite。
### 字號
我會定義一些與格柵系統原理相似的 class 來聲明字號。這些 class 能夠用於雙重標題分級,關於這點請閱讀 [Pragmatic, practical font-sizing in CSS](http://csswizardry.com/2012/02/pragmatic-practical-font-sizing-in-css)。
## 簡寫
**CSS 簡寫應當謹慎使用。**
編寫像 `background:red;` 這樣的屬性的確很省事,可是你這麼寫的意思實際上是同時聲明 `background-image:none; background-position:top left; background-repeat: repeat; `。雖然大多數時候這樣不會出什麼問題,可是哪怕只出一次問題就值得考慮要不要放棄簡寫了。這裏應當將其改成 `background-color:red;`。
相似的,像 `margin:0;` 這樣的聲明的確簡潔清爽,可是仍是應當 **儘可能寫清楚**。若是你只是想修改底邊的 `margin`,最好具體一些,寫成 `margin-bottom:0;`。
反過來,你須要聲明的屬性也要寫清楚,不要由於簡寫而波及其它屬性。例如若是你只想改掉底部的 `margin`,那就不要用會把其它邊距也清零的 `margin:0`。
簡寫雖然是好東西,可是注意切勿濫用。
## ID
在咱們開始處理選擇器以前,牢記這句話:
**在 CSS 裏堅定不要用 ID。**
在 HTML 裏 ID 能夠用於 JS 以及錨點定位,可是在 CSS 裏只要用 class,一個 ID 也不要用。
Class 的優點在於複用性,並且私有度也並不高。私有度很是容易致使問題,因此將其下降就尤其重要。ID 的私有度是 class 的 **255** 倍,因此在 CSS 中堅定不要使用。
## 選擇器
務必保持選擇器簡短高效。
經過頁面元素位置而定位的選擇器並不理想。例如 `.sidebar h3 span{}` 這樣的選擇器就是定位過於依賴相對位置,因此很難把 span 移到 h3 和 sidebar 外面並保持其樣式。
結構複雜的選擇器將會影響性能。選擇器結構越複雜(如 `.sidebar h3 span` 爲三層,`.content ul p a` 是四層),瀏覽器的消耗就越大。
儘可能使得樣式不依賴於其定位,儘可能保持選擇器簡潔清晰。
做爲一個總體,選擇器應當儘可能簡短(例如只有一層結構),可是 class 名則不該當過於簡略,例如 `.user-avatar` 就遠比 `.usr-avt` 好。
**牢記:** class 無所謂是否語義化;應當關注它們是否合理。不要強調 class 名要符合語義,而要注重使用合理且不會過期的名稱。
### 過分修飾的選擇器
由前文所述,過分修飾的選擇器並不理想。
過分修飾的選擇器是指像 `div.promo` 這樣的。極可能你只用 `.promo` 也能獲得相同的效果。固然你可能偶爾會須要用元素類型來修飾 class(例如你寫了一個 `.error` 並且想讓它在不一樣的元素類型中顯示效果不同,例如 `.error{ color:red; }` `div.error{ padding:14px;}`),可是大多數時候仍是應當儘可能避免。
再舉一個修飾過分的選擇器例子,`ul.nav li a{}`。如前文所說,咱們立刻就能夠刪掉 `ul` 由於咱們知道 `.nav` 是個列表,而後咱們就能夠發現 `a` 必定在 `li` 中,因此咱們就能將這個選擇器改寫成 `.nav a{}`。
### 選擇器性能
雖然瀏覽器性能日漸提高,渲染 CSS 速度愈來愈快,可是你仍是應當關注效率。使用間斷、沒有嵌套的選擇器,不把全局選擇器(`*{}`)用做核心選擇器,避免使用日漸複雜的 CSS3 新選擇器能夠避免這樣的問題。
譯註,核心選擇器:瀏覽器解析選擇器爲從右向左的順序,最右端的元素是樣式生效的元素,是爲核心選擇器。
## 使用 CSS 選擇器的目的
比起努力運用選擇器定位到某元素,更好的辦法是直接給你想要添加樣式的元素直接添加一個 class。咱們以 `.header ul{}` 這樣一個選擇器爲例。
假定這個 `ul` 就是這個網站的全站導航,它位於 header 中,並且目前爲止是 header 中惟一的 `ul` 元素。`.header ul{}` 的確能夠生效,可是這樣並非好方法,它很容易過期,並且很是晦澀。若是咱們在 header 中再添加一個 `ul` 的話,它就會套用咱們給這個導航部分寫的樣式,哪怕咱們設想的不是這個效果。這意味着咱們要麼要重構許多代碼,要麼給後面的 `ul` 新寫許多樣式來抵消以前的影響。
你的選擇器必須符合你要給這個元素添加樣式的緣由。思考一下, **「我定位到這個元素,是由於它是 `.header` 下的 `ul`,仍是由於它是個人網站導航?」**這將決定你應當如何使用選擇器。
確保你的核心選擇器不是類型選擇器,也不是高級對象或抽象選擇器。例如你在咱們的 CSS 中確定找不到諸如 `.sidebar ul{}` 或者 `.footer .media{}` 這樣的選擇器。
表達清晰:直接找到你要添加樣式的元素,而非其親元素。不要想固然地認爲 HTML 不會改變。 **用 CSS 直接命中你須要的元素,而非投機取巧。**
完整內容請參考個人文章 [Shoot to kill; CSS selector intent](http://csswizardry.com/2012/07/shoot-to-kill-css-selector-intent/)
## `!important`
只在起輔助做用的 class 上用 `!important`。用 `!important` 提高優先級也能夠,例如若是你要讓某條規則 **一直** 生效的話,能夠用 `.error{ color:red!important; }`。
避免主動使用 `!important`。例如 CSS 寫得很複雜時不要用它來取巧,要好好整理並重構以前的部分,保持選擇器簡短而且避免用 ID 將效果拔羣。
## 魔數與絕對定位
魔數(Magic Number)是指那些「湊巧有效果」的數字,這東西很是很差,由於它們只是治標不治本並且缺少拓展性。
例如 `.dropdown-nav li:hover ul{ top:37px; }` 把下拉菜單移動下來遠非良策,由於這裏的 37px 就是個魔數。37px 會生效的緣由是由於這時 `.dropbox-nav` 碰巧高 37px 而已。
這時你應該用 `.dropdown-nav li:hover ul{ top:100%; }`,也即不管 `.dropbox-down` 多高,這個下拉菜單都會往下移動 100%。
每當你要在代碼中放入數字的時候,請三思而行。若是你能用一個關鍵字(例如 `top:100%` 意即「從上面拉到最下面」)替換之,或者有更好的解決方法的話,就儘可能避免直接出現數字。
你在 CSS 中留下的每個數字,都是你許下而不肯遵照的承諾。
## 條件判斷
專門爲 IE 寫的樣式基本上都是能夠避免的,惟一須要爲 IE 專門處理的是爲了處理 IE 不支持的內容(例如 PNG)。
簡而言之,若是你重構 CSS 的話,全部的佈局和盒模型都不用額外兼容 IE。也就是說你基本上不用 `<!--[if IE 7]> element{ margin-left:-9px; } < ![endif]-->` 或者相似的兼容 IE 的寫法。
## Debugging
若是你要解決 CSS 問題的話, **先把舊代碼拿掉再寫新的** 。若是舊的 CSS 中有問題的話,寫新代碼是解決不了的。
把 CSS 代碼和 HTML 部分刪掉,直到沒有 BUG 爲止,而後你就知道問題出在哪裏了。
有時候寫上一個 `overflow:hidden` 或者其它能把問題藏起來的代碼的確效果立竿見影,可是 overflow 方面可能根本就沒問題。因此 **要治本,而不是單純治標**。
## 預處理器
我用 Sass。使用時應當 **靈活運用** 。用 Sass 能夠令你的 CSS 更強大,可是不要嵌套得太複雜。在 Vanilla CSS 中,只在必要的地方用嵌套便可,例如:
.header{}
.header .site-nav{}
.header .site-nav li{}
.header .site-nav li a{}
這樣的寫法在普通 CSS 裏徹底用不到。如下爲 **很差的** Sass 寫法:
.header{
.site-nav{
li{
a{}
}
}
}
若是你用 Sass 的話,儘可能這麼寫:
.header{}
.site-nav{
li{}
a{}
}