背景
CSS の誕生以来、基本的な構文とコアのメカニズムは本質的に変わっていません。その開発は、ほぼ完全に表現力の向上です。当初、Web ページにおける CSS の役割は補助的な装飾にすぎず、学習の容易さが最大の要件でしたが、現在では Web サイトの複雑さは異なり、ネイティブ CSS が開発者を圧倒しています。
言語の機能が不十分で、ユーザーの動作環境が他のオプションをサポートしていない場合、その言語は「コンパイル対象」言語になります。開発者は、開発に使用する別の高レベル言語を選択し、実際に実行するために低レベル言語にコンパイルします。
それで、フロントエンドの分野では天から大きな責任が与えられ、CSS プリプロセッサが誕生しました。古代言語である CSS は、別の方法で Web 開発のニーズに「再適応」しました。
プリプロセッサによって与えられた「スーパーパワー」
簡単に復習すると、CSS プリプロセッサはいくつかの重要な機能をもたらしてくれました。それらを浅いものから深いものまで以下に示します。 (どれだけ使用しても、どれだけ深くても、利益は得られます。)
ファイル分割
ページはますます複雑になり、ロードする必要がある CSS ファイルは大きくなり、大きなファイルを分割する必要があります。そうしないと、メンテナンスが困難になります。従来の CSS ファイル分割ソリューションは、基本的に CSS ネイティブの @import ディレクティブ、または HTML での複数の CSS ファイルの読み込みであり、通常、これらのソリューションはパフォーマンス要件を満たすことができません。
CSS プリプロセッサは @import ディレクティブの機能を拡張し、コンパイル プロセスを通じて分割されたファイルを 1 つの大きなファイルに再マージします。これにより、大きなファイルのメンテナンスが不便になるという問題が解決される一方で、小さなファイルを大量にロードするときのパフォーマンスの問題も解決されます。
モジュール化
ファイルのセグメント化の考え方をさらに一歩進めたものが「モジュール化」です。大きな 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中文网!