CSS為什麼要使用預處理器?

CSS為什麼要使用預處理器?


背景

CSS 自誕生以來,基本語法和核心機制一直沒有本質上的變化,它的發展幾乎全是表現力層面上的提升。最開始 CSS 在網頁中的作用只是輔助性的裝飾,輕便易學是最大的需求;然而如今網站的複雜度已經不可同日而語,原生 CSS 已經讓開發者力不從心。

當一門語言的能力不足而用戶的運行環境又不支持其它選擇的時候,這門語言就會淪為 “編譯目標” 語言。開發者將選擇另一門更高級的語言來進行開發,然後編譯到底層語言以便實際運行。

於是,在前端領域,天降大任於斯人也,CSS 預處理器應運而生。而 CSS 這門古老的語言以另一種方式 “重新適應” 了網頁開發的需求。

預處理器賦予我們的 “超能力”

簡單梳理一下,CSS 預處理器為我們帶來了幾項重要的能力,由淺入深排列如下。(不用在意你用到了多少,無論深淺,都是獲益。)

文件切分

頁面越來越複雜,需要加載的 CSS 文件也越來越大,我們有必要把大文件切分開來,否則難以維護。傳統的 CSS 文件切分方案基本上就是 CSS 原生的 @import 指令,或在 HTML 中加載多個 CSS 文件,這些方案通常不能滿足性能要求。

CSS 預處理器擴展了 @import 指令的能力,通過編譯環節將切分後的文件重新合併為一個大文件。這一方面解決了大文件不便維護的問題,另一方面也解決了一堆小文件在加載時的性能問題。

模塊化

把文件切分的思路再向前推進一步,就是 “模塊化”。一個大的 CSS 文件在合理切分之後,所產生的這些小文件的相互關係應該是一個樹形結構。

樹形的根結節一般稱作 “入口文件”,樹形的其它節點一般稱作 “模塊文件”。入口文件通常會依賴多個模塊文件,各個模塊文件也可能會依賴其它更末端的模塊,從而構成整個樹形。

以下是一個簡單的示例:

entry.styl
├─ base.styl
│ ├─ normalize.styl
│ └─ reset.styl
├─ layout.styl
│ ├─ header.styl
│ │ └─ nav.styl
│ └─ footer.styl
├─ section-foo.styl
├─ section-bar.styl
└─ ...

(入口文件 entry.styl 在編譯時會引入所需的模塊,生成 entry.css,然後被頁面引用。)

如果你用過其它擁有模塊機制的編程語言,應該已經深有體會,模塊化是一種非常好的代碼組織方式,是開發者設計代碼結構的重要手段。模塊可以很清晰地實現代碼的分層、複用和依賴管理,讓 CSS 的開發過程也能享受到現代程序開發的便利。

選擇符嵌套

選擇符嵌套是文件內部的代碼組織方式,它可以讓一系列相關的規則呈現出層級關係。在以前,如果要達到這個目的,我們只能這樣寫:

.nav {margin: auto /* 水平居中 */; width: 1000px; color: #333;}
.nav li {float: left /* 水平排列 */; width: 100px;}
.nav li a {display: block; text-decoration: none;}

這種寫法需要我們手工維護縮進關係,當上級選擇符發生變化時,所有相關的下級選擇符都要修改;此外,把每條規則寫成一行也不易閱讀,為單條聲明寫註釋也很尷尬(只能插在聲明之間了)。

在 CSS 預處理語言中,嵌套語法可以很容易地表達出規則之間的層級關係,為單條聲明寫註釋也很清晰易讀:

.nav
margin: auto // 水平居中
width: 1000px
color: #333
li
float: left // 水平排列
width: 100px
a
display: block
text-decoration: none

變量

在變更出現之前,CSS 中的所有屬性值都是 “幻數”。你不知道這個值是怎麼來的、它的什麼樣的意義。有了變量之後,我們就可以給這些 “幻數” 起個名字了,便於記憶、閱讀和理解。

接下來我們會發現,當某個特定的值在多處用到時,變量就是一種簡單而有效的抽象方式,可以把這種重複消滅掉,讓你的代碼更加 DRY。

我們來比較一下以下兩段代碼:

/* 原生 CSS 代碼 */
strong {
color: #ff4466;
font-weight: bold;

}
/* ... */
.notice {
color: #ff4466;
}
// 用 Stylus 來寫
$color-primary = #ff4466
strong
color: $color-primary
font-weight: bold
/* ... */
.notice
color: $color-primary

你可能已經意識到了,變量讓開發者更容易實現網站視覺風格的統一,也讓 “換膚” 這樣的需求變得更加輕鬆易行。

運算

光有變量還是不夠的,我們還需要有運算。如果說變量讓值有了意義,那麼運算則可以讓值和值建立關聯。有些屬性的值其實跟其它屬性的值是緊密相關的,CSS 語法無法表達這層關係;而在預處理語言中,我們可以用變量和表達式來呈現這種關係。

舉個例子,我們需要讓一個容器最多隻顯示三行文字,在以前我們通常是這樣寫的:

.wrapper {
overflow-y: hidden;
line-height: 1.5;
max-height: 4.5em; /* = 1.5 x 3 */
}

大家可以發現,我們只能用註釋來表達 max-height 的值是怎麼來的,而且註釋中 3 這樣的值也是幻數,還需要進一步解釋。未來當行高或行數發生變化的時候,max-height 的值和註釋中的算式也需要同步更新,維護起來很不方便。

接下來我們用預處理語言來改良一下:

.wrapper
$max-lines = 3
$line-height = 1.5
overflow-y: hidden
line-height: $line-height
max-height: unit($line-height * $max-lines, 'em')

乍一看,代碼行數似乎變多了,但代碼的意圖卻更加清楚了——不需要任何註釋就把整件事情說清楚了。在後期維護時,只要修改那兩個變量就可以了。

值得一提的是,這種寫法還帶來另一個好處。$line-height 這個變量可以是 .wrapper 自己定義的局部變量(比如上面那段代碼),也可以從更上層的作用域獲取:

$line-height = 1.5 // 全局統一行高
body
line-height: $line-height
.wrapper
$max-lines = 3
max-height: unit($line-height * $max-lines, 'em')
overflow-y: hidden

這意味著 .wrapper 可以向祖先繼承行高,而不需要為這個 “只顯示三行” 的需求把自己的行高寫死。有了運算,我們就有能力表達屬性與屬性之間的關聯,它令我們的代碼更加靈活、更加 DRY。

函數

把常用的運算操作抽象出來,我們就得到了函數。

開發者可以自定義函數,預處理器自己也內置了大量的函數。最常用的內置函數應該就是顏色的運算函數了吧!有了它們,我們甚至都不需要打開 Photoshop 來調色,就可以得到某個顏色的同色系變種了。

舉個例子,我們要給一個按鈕添加鼠標懸停效果,而最簡單的懸停效果就是讓按鈕的顏色加深一些。我們寫出的 CSS 代碼可能是這樣的:

.button {
background-color: #ff4466;
}
.button:hover {
background-color: #f57900;
}

我相信即使是最資深的視覺設計師,也很難分清 #ff4466 和 #f57900 這兩種顏色到底有什麼關聯。而如果我們的代碼是用預處理語言來寫的,那事情就直觀多了:

.button
$color = #ff9833
background-color: $color
&:hover
background-color: darken($color, 20%)

此外,預處理器的函數往往還支持默認參數、具名實參、arguments 對象等高級功能,內部還可以設置條件分支,可以滿足複雜的邏輯需求。

Mixin

Mixin 是 CSS 預處理器提供的又一項實用功能。Mixin 的形態和用法跟函數十分類似——先定義,然後在需要的地方調用,在調用時可以接受參數。它與函數的不同之處在於,函數用於產生一個值,而 Mixin 的作用是產生一段 CSS 代碼。

Mixin 可以產生多條 CSS 規則,也可以只產生一些 CSS 聲明。

一般來說,Mixin 可以把 CSS 文件中類似的代碼塊抽象出來,並給它一個直觀的名字。比如 CSS 框架可以把一些常用的代碼片斷包裝為 mixin 備用,在內部按需調用,或暴露給使用者在業務層調用。

舉個例子,我們經常會用到 clearfix 來閉合浮動。在原生 CSS 中,如果要避免 clearfix 代碼的重複,往往只能先定義好一個 .clearfix 類,然後在 HTML 中掛載到需要的元素身上:

/* 為 clearfix 定義一個類 */
.clearfix {...}
.clearfix::after {...}

...

...
<footer>.../<footer>

把表現層的實現暴露到了結構層,是不是很不爽?而在預處理器中,我們還可以選擇另一種重用方式:

// 為 clearfix 定義一個 mixin
clearfix()
...
&::after
...
// 在需要的元素身上調用
.info
clearfix()
footer
clearfix()

工程化

CSS 預處理語言無法直接運行於瀏覽器環境,這意味著我們編寫的源碼需要編譯為 CSS 代碼之後才能用於網頁。這似乎是一個門檻,需要我們付出 “額外” 的成本。

但在目前的大環境下,大多數項目的前端開發流程已經包含了構建環節,比如選擇任何一個腳本模塊化方案都是需要在部署時走一道打包程序的。所以對大多數團隊來說,這個門檻其實已經跨過去一大半了。

而一旦接受了這種設定,我們還可以享受到 “額外” 的福利。在給 CSS 的開發加入編譯環節的同時,還可以順道加入其它構建環節,比如代碼校驗、代碼壓縮、代碼後處理等等。

“代碼後處理” 是指 PostCSS 平臺上各類插件所提供的功能,光是 Autoprefixer 這一項就已經值回票價了。我們再也不需要在 CSS 代碼中手工添加瀏覽器前綴了,直接使用標準寫法,剩下的事情讓工具搞定吧!


分享到:


相關文章: