首頁  >  文章  >  web前端  >  淺談 CSS 預處理器(一):為什麼要使用預處理器

淺談 CSS 預處理器(一):為什麼要使用預處理器

高洛峰
高洛峰原創
2017-02-09 13:06:101443瀏覽

背景

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 {...}
<!-- 挂载到这两个元素身上 -->
<div class="info clearfix">...</div>
...
<footer class="clearfix">...</footer>

把表现层的实现暴露到了结构层,是不是很不爽?而在预处理器中,我们还可以选择另一种重用方式:

// 为 clearfix 定义一个 mixin
clearfix()
    ...
    &::after
        ...

// 在需要的元素身上调用
.info
    clearfix()

footer
    clearfix()

工程化

CSS 预处理语言无法直接运行于浏览器环境,这意味着我们编写的源码需要编译为 CSS 代码之后才能用于网页。这似乎是一个门槛,需要我们付出 “额外” 的成本。

但在目前的大环境下,大多数项目的前端开发流程已经包含了构建环节,比如选择任何一个脚本模块化方案都是需要在部署时走一道打包程序的。所以对大多数团队来说,这个门槛其实已经跨过去一大半了。

而一旦接受了这种设定,我们还可以享受到 “额外” 的福利。在给 CSS 的开发加入编译环节的同时,还可以顺道加入其它构建环节,比如代码校验、代码压缩、代码后处理等等。

“代码后处理” 是指 PostCSS 平台上各类插件所提供的功能,光是 Autoprefixer 这一项就已经值回票价了。我们再也不需要在 CSS 代码中手工添加浏览器前缀了,直接使用标准写法,剩下的事情让工具搞定吧!

更多浅谈 CSS 预处理器(一):为什么要使用预处理器相关文章请关注PHP中文网!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn