> 原文出處:http://www.w3cplus.com/css3/css-secrets/css-coding-tips.html
## 縮簡代碼
在軟件開發過程中,保持代碼的簡潔和可維護性是最大的挑戰,對于 CSS 來說,同樣如此。實際上,可維護代碼的一個重要特性就是要**縮簡需求變化時所需修改的代碼量**。假設放大一個按鈕需要對十處代碼做出修改,那么你就有可能遺漏其中的一些細節,如果這些代碼本來就不是你寫的,那么就更有可能發生這種疏漏。即使需要修改的細節顯而易見,即使你準確地找到了這些細節,那么你也在無形中浪費了大量的時間——這些時間本該有更大的創造力。
此外,這并不只是應對未來的需求變化。可擴展的 CSS 在完成首次編寫后,只需要用少量代碼創建適當的變量,那么進行重寫和覆蓋時所需的代碼量就會很少。下面讓我們來看一個例子。
請先看一下下面的 CSS,它被用來美化下圖所示的按鈕:

~~~
padding: 6px 16px;
border: 1px solid #446d88;
background: #58a linear-gradient(#77a0bb, #58a);
border-radius: 4px;
box-shadow: 0 1px 5px gray;
color: white;
text-shadow: 0 -1px 1px #335166;
font-size: 20px;
line-height: 30px;
~~~
這段代碼在可維護性上有幾點問題,讓我們來修改一下。首先看到的就是字體的單位。如果我們想要更改字體大小(比如為了創建更大、更顯眼的按鈕),那么就需要同時修改行間距,因為它們在這里使用的都是絕對值。此外,這里的行間距并不能有效地與字體大小關聯起來,以至于我們需要手動計算各種字體大小下的行間距。**當屬性值相互關聯時,應該在代碼中體現它們的關聯性。**對于上述的代碼,行間距是行高的?`150%`,所以,使用下面的代碼將更具可維護性:
~~~
font-size: 20px;
line-height: 1.5;
~~~
既然我們都寫成了這樣,為什么還要給字體大小指定一個絕對數值?雖然絕對數值易于使用,但是每次需求變化時你就需要重新修改,比如現在我們想讓父級字體更大一些,那么就需要在樣式表中使用絕對數值修改每一條相關的樣式規則,顯然這是不可取的。更好的方式是使用百分比或者類似?**`em`**?的單位:
~~~
font-size: 125%; /* 假設父級字體為 16px */
line-height: 1.5;
~~~
現在,如果我修改了父級字體大小,那么按鈕也會相應的變大。不過,按鈕看起來和之前不大一樣了,如下圖所示:

這是因為其他的特效還是和之前一樣袖珍,仍然不具有伸縮性。只需要使用類似?**`em`**?的單位,我們就可以將其他特效變成可伸縮的,最終所有的屬性值都關聯到了字體大小上。至此,我們只需修改字體大小就能控制整個按鈕的大小了。
~~~
padding: .3em .8em;
border: 1px solid #446d88;
background: #58a linear-gradient(#77a0bb, #58a);
border-radius: .2em;
box-shadow: 0 .05em .25em gray;
color: white;
text-shadow: 0 -.05em .05em #335166;
font-size: 125%;
line-height: 1.5;
~~~
現在,按鈕看起來很像是原始版本的放大版:

*在這里,我們想要字體大小與父級字體相關聯,所以使用了?**`em`**。在某些情況下,你想要讓字體和根節點的字體相關聯(比如`<html>`?節點的字體),但是這時使用?**`em`**?就會讓計算變得非常復雜。此時,最好使用?`rem`?單位。雖然“相關性”在 CSS 中很重要,但你關聯前需要考慮下什么元素需要被關聯在一起。 *
請注意,在某些屬性上我們仍然使用了絕對數值。**對于按鈕的哪些效果需要可伸縮,哪些不需要可伸縮這一問題,應該主觀判斷而不能客觀要求。**比如此處的按鈕,不論按鈕如果縮放,我們始終要求它的邊框粗細為?`1px`。
不過,通常我們所需要修改的不只是按鈕的尺寸。對配色的修改也是一個很重要的方面。比如,如果我們想要創建一個紅色的“取消”按鈕,或者是綠色的 “OK” 按鈕又該如何做呢?一般來說,我們至少需要重寫四條樣式(**`border-color`**,**`background`**,**`box-shadow`**,**`text-shadow`**)。不用多說你也會理解,重新計算主體顏色(**`#58a`**)的亮度、理清所需顏色的亮度,將是一份非常麻煩的事情。此外,如果我們將按鈕添加到了非白色背景的頁面中,又該如何修改呢?實際上,上述按鈕的灰色陰影只在白色背景下才會效果明顯。
一個簡單的修改方式是,對主體顏色的亮度疊加半透明的黑白色:
~~~
padding: .3em .8em;
border: 1px solid rgba(0,0,0,.1);
background: #58a linear-gradient(hsla(0,0%,100%,.2),transparent);
border-radius: .2em;
box-shadow: 0 .05em .25em rgba(0,0,0,.5);
color: white;
text-shadow: 0 -.05em .05em rgba(0,0,0,.5);
font-size: 125%;
line-height: 1.5;
~~~
> 提示:使用?`HSLA`?而不是?`RGBA`?表示半透明白色的好處在于,由于無需重復,它的字符更少,編寫的更快。
接下來,我們可以使用不同的顏色重寫背景顏色了,如下圖所示:

~~~
button.cancel {
background-color: #c00;
}
button.ok {
background-color: #6b0;
}
~~~
現在,整個按鈕的樣式更加靈活了。不過,這個示例并沒有闡述怎樣讓代碼保持簡潔,更多技巧請閱讀接下來的內容。
### 可維護性 VS 簡潔
有些時候,**代碼的可維護性和簡潔是相互敵對的。**就像是前面的按鈕示例,最終的代碼量比最初的代碼量還增加了一些。請思考一下下面的這段代碼,它的作用是為某元素設置邊框粗細,除左邊框外都設置為?`10px`:
~~~
border-width: 10px 10px 10px 0;
~~~
雖然只是一條樣式,但是當我們修改邊框的粗細時,至少要修改三處地方。如果我們將該樣式寫成兩句,那么修改起來就會更加方便,而且可以預見的是也更加冗余閱讀:
~~~
border-width: 10px;
border-left-width: 0;
~~~
> 有些人可能會爭論說?`em`?單位才是 CSS 的第一個變量,因為它引用了?**`font-size`**?的值。大多數的百分比也是同樣的角色,雖然這并不令人感到興奮。
### currentColor
**[CSS Color Level 3](http://www.w3.org/TR/css3-color/)**?為開發者提供了許多新的顏色關鍵字,比如?**lightgoldenrodyellow**——雖然目前來看這并沒多大用處。不過,其中也有一些非常有用的顏色關鍵字,比如從 SVG 借鑒來的?**`currentColor`**。實際上,該屬性并不是一個固定不變的顏色值,它會根據?**`color`**?的屬性值生成相應的屬性值——這讓它成為了 CSS 中的第一個變量。雖然它的作用有限,但意義重大。
> 有關于`currentColor`相關的介紹,可以閱讀《[使用CSS的currentColor變量擴展顏色級聯](http://www.w3cplus.com/css3/extending-the-color-cascade-with-the-css-currentcolor-variable.html)》一文。
讓我們來舉個例子,假設要讓所有的水平線(`<hr>`)和文本保持統一的顏色,那么就可以使用?**currentColor**:
~~~
hr {
height: .5em;
background: currentColor;
}
~~~
你可能已經注意到了,現有的很多屬性就有相同的特性。比如,如果沒有給邊框添加顏色,那么邊框的顏色會被渲染為文本的顏色。這是因為?**`currentColor`**?同樣是許多 CSS 顏色屬性的初始值:**`border-color`**、**`box-shadow`**、**`outline-color`**……
未來,當我們可以 CSS 的原生屬性控制顏色時,那時我們能夠使用更多的變量,而?**`currentColor`**?也將會更有用處。
### 繼承
雖然很多開發者意識到了?**`inherit`**?關鍵字的重要性,但往往會忘記使用它。**`inherit`**?可以被應用到任何的 CSS 屬性上,并且會根據父級元素的屬性計算出恰當的值(如果是偽元素,那么就會根據當前元素屬性來計算)。比如,讓表單元素和頁面其他元素具有相同的字體,無需一一指定,直接使用?**`inherit`**?即可:
~~~
input, select, button { font: inherit; }
~~~
同樣的道理,讓超鏈接和頁面文本具有相同的顏色,也可以使用?**`inherit`**:
~~~
a { color: inherit; }
~~~
**`inherit`**?關鍵字也同樣適用于?**`background`**?之類的屬性。比如,創建一個拼寫氣泡(speech bubbles),讓它自動繼承輸入框的背景和邊框,如下圖所示:

*一個拼寫檢查氣泡,它獲得了父級元素的背景色和邊框。*
~~~
.callout {
position: relative;
}
.callout::before {
content: "";
position: absolute;
top: -.4em;
left: 1em;
padding: .35em;
background: inherit;
border: inherit;
border-right: 0;
border-bottom: 0;
transform: rotate(45deg);
}
~~~
## 相信你的眼睛,而不是數字
人類的雙眼并不是完美的輸入設備(input device)。有時候,精確的尺寸卻在視覺上呈現了一種不精確的感覺。比如在視覺設計中,眾所周知的是當元素垂直居中時,有些細節是無法察覺的。相反,元素需要略高于幾何中心,才能被視為垂直居中。如下圖所示:

*在第一個矩形中,棕色的正方形經數學計算是垂直居中的,但視覺上并不是;在第二個矩形中,棕色的正方形被放置在了略高于中心的位置,但視覺上更像是垂直居中。*
同樣在經典的設計中,眾所周知的是類似 “O” 的圓形字符需要略大于矩形字符,只是因為我們的視覺會認為圓形略小與矩形。如下圖所表現的效果。

*圓形雖然看起來更小,但它的邊界實際上和正方形是一致的。*
**類似的視覺錯覺在視覺設計中非常常見**,往往需要進行適當的修正。一個非常常見的例子就是包含文字的容器內邊距。這一問題的原因是容器忽略了文本的數量,可能是一個單詞,也可能是好幾段文字。如果我們為容器的四個邊指定了相同的內邊距,那么就會像下圖所示:

*為容器的四個內邊距添加相同的數值(`.5em`),但是容器內的文字讓上下兩邊的內邊距顯得比左右兩邊更大一些。*
容器看起來并不協調。究其原因就是**在水平方向上字體形狀更加連貫,導致我們的眼睛認為垂直方向上的多余空間都是內邊距**。因此,我們在垂直方向上減少內邊距,才能讓四邊的內邊距看起來一致。如下圖所示:

*讓左右兩邊的內邊距更大一些(`.3em .7em`),整體看起來更協調了。*
## 響應式設計
[RWD (Responsive Web Design,響應式設計)](http://www.w3cplus.com/responsive)在過去幾年很流行。然而,大家的焦點往往放在了響應式網站的重要性上,而忽略了什么才是響應式網站的優勢。
開發響應式網站,常見的方式就是在多種分辨率上測試一個網站,然后通過不斷地疊加媒體查詢修正出現的問題。然而,每一次使用媒體查詢都增加了未來維護 CSS 的負擔,它們實際上并不值得被添加。未來維護這段 CSS 代碼的開發者需要反復檢查是否所有的媒體查詢都生效了,而且還可能會去修改這些媒體查詢,而這么繁多的細節往往又容易被遺忘,繼而導致了某些斷層。你添加的媒體查詢語句越多,那么 CSS 的碎片化現象就會越嚴重。
這并不是說使用媒體查詢是一個糟糕的方式。**使用方法得當,才會事半功倍。**不過,媒體查詢應該是我們的終極解決方案,只有當其他方法都無法完成響應式設計,或者需要在或大或小的視區上完全更改頁面布局時(比如將側邊欄轉換為水平方向),才應該使用媒體查詢。之所以這么說,是因為媒體查詢本質上不是用來修復連續性錯誤的。媒體查詢主用于指定視區的閾值(或者稱之為斷點),除非其他的代碼也是靈活可擴展的,否則媒體查詢只能在特定的分辨率起作用,本質上并沒有解決問題。
不必多說相信你也會理解,**媒體查詢的斷點不應該由具體的設備來決定,而應該由設計本身來決定。**一方面是因為不同尺寸的設備太多(特別是我們需要為未來的設備而考慮),網站需要盡可能地適應各種分辨率;另一方面是因為在電腦桌面上可能以任意尺寸的窗口打開網頁。如果你自信地認為自己的設計可以應用到各種窗口大小,那又何必擔心某些特定設備的分辨率呢?
根據第九頁“縮簡代碼”一節的原則來編寫媒體查詢,將會讓你免于不斷地重寫斷點內的樣式,減少斷點內的問題。
這里有一些技巧,可以讓你避免添加無用的媒體查詢:
* 使用百分比而不是固定寬度。如果不能使用百分比,那么就是用與視窗(Viewport)相關的單位(`vw`,?`vh`,?`vmin`,?`vmax`),它們能夠根據視區的寬度或高度生成相應的數值。
* 如果你想為分辨率更高的頁面使用固定寬度,那么請使用?**`max-width`**,而不要使用?**`width`**,這樣做的好處是即使沒有使用媒體查詢,仍然能夠確保頁面適配較低的分辨率。
* 不要忘記將可替換元素設為?**`max-width: 100%;`**,常見的可替換元素包括:**`img`**、**`object`**、**`video`**?和?**`iframe`**。
* 當需要將背景圖填充整個容器時,使用?**`background-size: cover;`**?可以讓圖片在容器改變大小時仍然保持填充,增加代碼的可維護性。不過,一定要牢記帶寬不是無限的,在移動端頁面加載大尺寸的圖片,再使用 CSS 進行縮放是非常不明智的做法。
* 在柵格布局中添加圖片或者其他元素時,最好瀏覽器根據視區的寬度來決定列數。使用彈性盒布局([Flexible Box,又被成為 Flexbox](http://www.w3cplus.com/blog/tags/157.html))、聲明為**`display: inline-block`**?或者讓文本環繞圖片,都可以達到這一目的。
* 當使用多欄布局時,應該使用?**`column-width`**?而不是?**`column-count`**,這樣的好處即使分辨率很低,內容也不會擁擠在一起,而會正常排布在一列之中。
通常來說,我們需要堅守的原則就是:**在媒體查詢的斷點中,使用流式布局和相對大小**。當設計足夠靈活時,創建響應式布局并不會使用過多的媒體查詢語句。在 2010 年末,Basecamp 的設計師關于這一話題寫道:
> “可以證明的是,只需要在最終的產品上增加少許的 CSS 媒體查詢語句,就可以讓布局適應各種設備。如此簡單的關鍵在于,布局本身就是流式的。所以對于視區較窄的小尺寸屏幕,我們所要做的就是壓縮外邊距,增加可利用空間,以及更改側邊欄布局。” ——?[Experimenting with responsive design in Iterations](https://signalvnoise.com/posts/2661-experimenting-with-responsive-design-in-iterations)
如果你發現自己需要大量的媒體查詢語句適應不同尺寸的屏幕,那么你需要重新審視一下自己的代碼,因為十有八九,問題的根據不在于響應式的設計。
> 建議在媒體查詢中使用?`em`?而不是?`px`。使用?`em`?可以在布局發生改變時自動進行縮放。
## 明智地使用簡寫形式
對于下面的兩行代碼,你可能會知道它們之間的差異:
~~~
background: rebeccapurple;
background-color: rebeccapurple;
~~~
前者是一個簡寫寫法,它會創建一個顏色為?**`rebeccapurple`**?的背景,而后者(**`background-color`**)可能會得到一個粉色的漸變、一只貓的背景圖……這是因為在后者之外,可能還存在一個?**`backgound-image`**?屬性在發揮作用。這就是使用普通寫法(longhands,比如這里使用的?**`background-size`**)時常會發生的問題:無法重設其他的屬性,繼而影響了最終的效果。
當然,你可以重設其他所有的屬性(這里就不演示了),但你很有可能會有所遺漏。或者,CSS WG 在未來會引入更多的普通寫法屬性,那么你的代碼又會失效。除非刻意地為某些元素使用普通寫法屬性,比如我們在第九頁“縮簡代碼”一節中對按鈕顏色所做的處理,否則不要畏懼縮寫形式,它們是優秀的防御性代碼,可以有效在未來保持可用性。
組合使用普通寫法和簡寫寫法也很有用。當某些屬性的屬性值是由逗號分隔的參數列表,那么使用組合方式就可以讓減少代碼中的重復,比如?**`background`**?屬性。下面的示例就很好地解釋了這一做法:
~~~
background: url(tr.png) no-repeat top right / 2em 2em,
url(br.png) no-repeat bottom right / 2em 2em,
url(bl.png) no-repeat bottom left / 2em 2em;
~~~
請注意,其中?**`background-size`**?和?**`background-repeat`**?的屬性值對于每張圖片都是相同的,而且還重復了三次。我們可以將重復的屬性值移動到全寫屬性中,然后該屬性根據 CSS 參數列表的擴展規則,擴展到所有相關列表的項目中:
~~~
background: url(tr.png) top right,
url(br.png) bottom right,
url(bl.png) bottom left;
background-size: 2em 2em;
background-repeat: no-repeat;
~~~
現在,我們只需要修改一次,就可以改變?**`background-size`**?和?**`background-repeat`**?屬性了。
## 我應該使用預處理器嗎?
你也許已經聽過類似?[LESS](http://lesscss.org/)、[Sass](http://sass-lang.com/)?和?[Stylus](http://learnboost.github.io/stylus)?等預處理器的大名了。它們為編寫 CSS 提供了許多方便的特性,比如變量、混合宏、函數、嵌套規則、顏色操縱方法等等。
**合理使用預處理器,可以在大型項目中保持代碼的簡潔和靈活性**,而原生的 CSS 由于功能的缺乏往往限制了我們的開發。當我們堅持代碼的健壯性、靈活性以及簡潔時,我們就會時常受制于語言的能力。此外,預處理器本身也有一些缺陷:
* 無法追蹤 CSS 文件的大小和復雜性。簡潔短小的代碼經過預處理器可能就會編譯出冗雜的 CSS 代碼來。
> 冷知識:怪異的簡寫語法 你可能已經注意到了,在簡寫寫法和普通寫法的示例中,為?**`background`**?簡寫寫法指定**`bacground-size`**?時,需要額外添加?**`background-position`**?屬性(雖然屬性值就是默認值),并且需要使用反斜線分隔它們。為什么此類簡寫寫法的語法如此怪異呢? 這么做主要是為了消除歧義。雖然在這個示例中?**`top right`**?很明顯是?**`background-position`**?的屬性、**`2em 2em`**?是?**`background-size`**?的屬性,而且這些屬性也是和順序無關的。但是,對于?**`50% 50%`**,你覺得它是?**`background-size`**?的屬性,還是?**`background-position`**?的屬性呢?當你使用普通寫法時,CSS 解析器能夠了解你的意思,但是使用普通寫法時,解析器就無法從?**`50% 50%`**?這一屬性推斷出它所指向的屬性了。這就是在這里使用反斜線的目的了。
對于大多數的簡寫寫法,很少有這種歧義,而且屬性的順序也沒有限制。不過,讓屬性值的順序對應合理的語法是一種非常好的做法,可以有效避免混淆和錯誤。如果你熟悉正則表達式和語法,那么也可以根據相關規范查看語法屬性——這可能是判斷屬性值是否有特定順序的最快方法。
* 因為在開發者工具中看到的 CSS 并不是你寫的 CSS(由預處理器編譯生成的),調試錯誤變得更加困難。鑒于可以通過 SourceMaps 獲得調試支持,這已經不是什么難題了。SourceMap 是一個很棒的新技術,它可以告知瀏覽器生成的 CSS 在預處理器中的 CSS 中的行號,以此來緩解這個問題。
* 在開發流程中,使用預處理器具有一定的延遲。雖然它們的編譯速度很快,但仍然會占用一定的時間編譯 CSS,而在編譯結束前你只能等待。
* 參與到我們代碼庫中的人,需要付出更多的努力以理解預處理器中的概念。對于我們的合作者,要么他們是熟悉預處理器的人,要么我們就必須教他們使用。所以我們對合作伙伴的選擇受到了限制,不然我們就得花費額外的時間訓練他們,二者兩者都不是最優的方案。
* 不要忘記抽象泄露法則的教誨:“所有有意義的抽象,在一定程度上都是有漏洞的。”預處理器是人類開發的,就像人類開發的其他非凡語言,它們都是有漏洞的——這些漏洞往往難以察覺,而我們往往不會懷疑預處理器出錯了,而會懷疑是 CSS 寫錯了。
除了上述列出的問題,預處理器也讓開發者深深地依賴上了它們,即使在某些無需使用預處理器的小項目中,開發者也會慣性使然地使用它們,而開發者所不知道的,預處理器的大多特性都會在未來添加到原生的 CSS 中。驚喜嗎?是的,**許多預處理器引以為傲的特性都會被融入原生的 CSS 中:**
* 已有一個類似變量的自定義屬性草案被提出([CSS Custom Properties for Cascading Variables](http://www.w3.org/TR/css-variables-1/)?)
* 源自 CSS Values & Units Level 3 的函數?**`calc()`**?不僅功能強大,而且也廣受支持。
* [CSS Color Level 4](http://dev.w3.org/csswg/css-color)?中的?**color()**?方法就會提供操縱顏色的功能。
* 在 CSS WG 中已經就規則嵌套開展了多場嚴肅的討論,并形成了一份草案。
請注意,上述提到的原生特性通常比預處理器提供的特性更加強大,因為它們是動態的。比如,預處理器是無法計算類似?**`100% - 50px`**?的表達式,因為在頁面渲染完成前,預處理器是無法知道這里的百分比對應的具體數值是多少。與之相比,原生 CSS 的**`calc()`**?方法則可以輕松勝任這項工作。相同的是,預處理器的變量也無法像下面一樣使用:
~~~
ul { --accent-color: purple; }
ol { --accent-color: rebeccapurple; }
li { background: var(--accent-color); }
~~~
你能理解這里做了什么嗎?在有序列表中,列表項的背景色將會使用?**`rebeccapurple`**,而在無序列表中,列表項的背景色將會使用?**`purple`**。試試看預處理能不能做到!在這個示例中,雖然我們只使用了后代選擇器,但重點在于展示原生 CSS 變量的動態性。
[](http://myth.io/)
*[Myth](http://myth.io/)是一個處于實驗階段的預處理器,它專注于模擬原生 CSS 的特性,而不是引入新的語法,非常類似于一個 CSS 的膩子腳本。*
因為上述的原生 CSS 特性尚未獲得良好的支持,所以在大多數情況下使用預處理器是不可避免的。我的建議是在項目中以純 CSS 起步,當不能保持代碼的簡潔時,切換為預處理器。為了避免完全依賴預處理器或在不適合的項目中使用預處理器,所以你要慎重的使用它們,而不是盲目的在新項目之初就使用它們。
如果你們想知道的話,那么我要告訴你接下章節中示例的樣式就是使用 SCSS 編寫的,雖然最初使用的是純 CSS,但當代碼變得復雜之后,為了可維護性切換為了 SCSS。誰說 CSS 和預處理器只能用于 Web?