前言
你好,我是魔法哥。我是一名傳統的前端開發者,我的很大一部分工作就是為各種型別的網頁寫 CSS,寫了很多年。
我從三年前開始接觸並使用 CSS 預處理,如魚得水,相見恨晚。因此,我感覺有必要寫些文章來總結一下這方面的心得。如果你是一位還沒有接觸前處理器的 CSS 開發者,希望我的文章能夠幫助你輕鬆開始!
(注:本文的示例程式碼均採用 Stylus 作為 CSS 預處理語言。)
背景
CSS 自誕生以來,基本語法和核心機制一直沒有本質上的變化,它的發展幾乎全是表現力層面上的提升。最開始 CSS 在網頁中的作用只是輔助性的裝飾,輕便易學是最大的需求;然而如今網站的複雜度已經不可同日而語,原生 CSS 已經讓開發者力不從心。
當一門語言的能力不足而使用者的執行環境又不支援其它選擇的時候,這門語言就會淪為 “編譯目標” 語言。開發者將選擇另一門更高階的語言來進行開發,然後編譯到底層語言以便實際執行。
於是,在前端領域,天降大任於斯人也,CSS 前處理器應運而生。而 CSS 這門古老的語言以另一種方式 “重新適應” 了網頁開發的需求。
前處理器賦予我們的 “超能力”
簡單梳理一下,CSS 前處理器為我們帶來了幾項重要的能力,由淺入深排列如下。(不用在意你用到了多少,無論深淺,都是獲益。)
檔案切分
頁面越來越複雜,需要載入的 CSS 檔案也越來越大,我們有必要把大檔案切分開來,否則難以維護。傳統的 CSS 檔案切分方案基本上就是 CSS 原生的 @import
指令,或在 HTML 中載入多個 CSS 檔案,這些方案通常不能滿足效能要求。
CSS 前處理器擴充套件了 @import
指令的能力,通過編譯環節將切分後的檔案重新合併為一個大檔案。這一方面解決了大檔案不便維護的問題,另一方面也解決了一堆小檔案在載入時的效能問題。
模組化
把檔案切分的思路再向前推進一步,就是 “模組化”。一個大的 CSS 檔案在合理切分之後,所產生的這些小檔案的相互關係應該是一個樹形結構。
樹形的根結節一般稱作 “入口檔案”,樹形的其它節點一般稱作 “模組檔案”。入口檔案通常會依賴多個模組檔案,各個模組檔案也可能會依賴其它更末端的模組,從而構成整個樹形。
以下是一個簡單的示例:
1 2 3 4 5 6 7 8 9 10 11 |
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 的開發過程也能享受到現代程式開發的便利。
選擇符巢狀
選擇符巢狀是檔案內部的程式碼組織方式,它可以讓一系列相關的規則呈現出層級關係。在以前,如果要達到這個目的,我們只能這樣寫:
1 2 3 |
.nav {margin: auto /* 水平居中 */; width: 1000px; color: #333;} .nav li {float: left /* 水平排列 */; width: 100px;} .nav li a {display: block; text-decoration: none;} |
這種寫法需要我們手工維護縮排關係,當上級選擇符發生變化時,所有相關的下級選擇符都要修改;此外,把每條規則寫成一行也不易閱讀,為單條宣告寫註釋也很尷尬(只能插在宣告之間了)。
在 CSS 預處理語言中,巢狀語法可以很容易地表達出規則之間的層級關係,為單條宣告寫註釋也很清晰易讀:
1 2 3 4 5 6 7 8 9 10 |
.nav margin: auto // 水平居中 width: 1000px color: #333 li float: left // 水平排列 width: 100px a display: block text-decoration: none |
變數
在變更出現之前,CSS 中的所有屬性值都是 “幻數”。你不知道這個值是怎麼來的、它的什麼樣的意義。有了變數之後,我們就可以給這些 “幻數” 起個名字了,便於記憶、閱讀和理解。
接下來我們會發現,當某個特定的值在多處用到時,變數就是一種簡單而有效的抽象方式,可以把這種重複消滅掉,讓你的程式碼更加 DRY。
我們來比較一下以下兩段程式碼:
1 2 3 4 5 6 7 8 9 10 11 |
/* 原生 CSS 程式碼 */ strong { color: #ff4466; font-weight: bold; } /* ... */ .notice { color: #ff4466; } |
1 2 3 4 5 6 7 8 9 10 11 |
// 用 Stylus 來寫 $color-primary = #ff4466 strong color: $color-primary font-weight: bold /* ... */ .notice color: $color-primary |
你可能已經意識到了,變數讓開發者更容易實現網站視覺風格的統一,也讓 “換膚” 這樣的需求變得更加輕鬆易行。
運算
光有變數還是不夠的,我們還需要有運算。如果說變數讓值有了意義,那麼運算則可以讓值和值建立關聯。有些屬性的值其實跟其它屬性的值是緊密相關的,CSS 語法無法表達這層關係;而在預處理語言中,我們可以用變數和表示式來呈現這種關係。
舉個例子,我們需要讓一個容器最多隻顯示三行文字,在以前我們通常是這樣寫的:
1 2 3 4 5 |
.wrapper { overflow-y: hidden; line-height: 1.5; max-height: 4.5em; /* = 1.5 x 3 */ } |
大家可以發現,我們只能用註釋來表達 max-height
的值是怎麼來的,而且註釋中 3
這樣的值也是幻數,還需要進一步解釋。未來當行高或行數發生變化的時候,max-height
的值和註釋中的算式也需要同步更新,維護起來很不方便。
接下來我們用預處理語言來改良一下:
1 2 3 4 5 6 7 |
.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
自己定義的區域性變數(比如上面那段程式碼),也可以從更上層的作用域獲取:
1 2 3 4 5 6 7 8 9 10 |
$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 程式碼可能是這樣的:
1 2 3 4 5 6 |
.button { background-color: #ff4466; } .button:hover { background-color: #f57900; } |
我相信即使是最資深的視覺設計師,也很難分清 #ff4466
和 #f57900
這兩種顏色到底有什麼關聯。而如果我們的程式碼是用預處理語言來寫的,那事情就直觀多了:
1 2 3 4 5 6 |
.button $color = #ff9833 background-color: $color &:hover background-color: darken($color, 20%) |
此外,前處理器的函式往往還支援預設引數、具名實參、arguments
物件等高階功能,內部還可以設定條件分支,可以滿足複雜的邏輯需求。
Mixin
Mixin 是 CSS 前處理器提供的又一項實用功能。Mixin 的形態和用法跟函式十分類似——先定義,然後在需要的地方呼叫,在呼叫時可以接受引數。它與函式的不同之處在於…………
……
……