CSS 預處理器是什麼?通常來講,它們基於 CSS 擴展了一套屬於本身的 DSL,來解決咱們書寫 CSS 時難以解決的問題:css
語法不夠強大,好比沒法嵌套書寫致使模塊化開發中須要書寫不少重複的選擇器;web
沒有變量和合理的樣式複用機制,使得邏輯上相關的屬性值必須以字面量的形式重複輸出,致使難以維護。瀏覽器
因此這就決定了 CSS 預處理器的主要目標:提供 CSS 缺失的樣式層複用機制、減小冗餘代碼,提升樣式代碼的可維護性。這不是錦上添花,而偏偏是雪中送炭。微信
網上已經有很多對比目前最主流的三個預處理器 Less、Sass 和 Stylus(按字母順序排名)的文章了,可是彷佛都不是很詳細,或者內容有些過期。下面我會更詳細地探討一下這三種預處理器的特性和它們的差別。app
下面主要會分爲以下幾方面來討論:less
基本語法ide
嵌套語法模塊化
變量函數
@import工具
混入
繼承
函數
邏輯控制
事先聲明一下,平時我在開發中主要使用的是 Less,因此可能對 Sass 和 Stylus 的熟悉程度稍差一些,比較時主要參考三者官網的語言特性說明,有一些正在開發的功能可能會遺漏。
Less 的基本語法屬於「CSS 風格」,而 Sass、Stylus 相比之下激進一些,利用縮進、空格和換行來減小須要輸入的字符。不過區別在於 Sass、Stylus 同時也兼容「CSS 風格」代碼。多一種選擇在更靈活的同時,在團隊開發中也免不了增長更多約定來保持風格統一。而對我的而言,語法風格按本身口味選擇便可。
注:後面的 Sass 代碼會用被更多人接受的 SCSS 風格給出。
Less & SCSS:
.box {
display: block;
}
Sass:
.box
display: block
Stylus:
.box
display: block
三者的嵌套語法都是一致的,甚至連引用父級選擇器的標記 & 也相同。區別只是 Sass 和 Stylus 能夠用沒有大括號的方式書寫。以 Less 爲例:
.a {
&.b {
color: red;
}
}
生成的 CSS 爲:
.a.b {
color: red;
}
除了規則集的嵌套,Sass 額外提供了一個我我的認爲比較另(jī)類(lèi)的「屬性嵌套」:
.funky {
font: {
family: fantasy;
size: 30em;
weight: bold;
}
}
三者都支持用 & 在嵌套的規則集中引用上層的選擇器,這能夠是嵌套書寫 CSS 時的「慣例」了。語法相同,可是邏輯上有些許差別。在一個選擇器中用兩次以上 & 且父選擇器是一個列表時,Less 會對選擇器進行排列組合,而 Sass 和 Stylus 不會這麼作。
也就是說,假設上層選擇器爲 .a, .b,則內部的 & & 在 Less 中會成爲 .a .a, .a .b, .b .a, .b .b,而 Sass 和 Stylus 則輸出 .a .a, .b .b。
假設咱們要用預處理器書寫 WHATWG 推薦的 section 標題樣式,在 Less 中能夠方便地書寫爲:
article, aside, nav, section {
h1 {
margin-top: 0.83em; margin-bottom: 0.83em; font-size: 1.50em;
}
& & h1 {
margin-top: 1.00em; margin-bottom: 1.00em; font-size: 1.17em;
}
& & & h1 {
margin-top: 1.33em; margin-bottom: 1.33em; font-size: 1.00em;
}
& & & & h1 {
margin-top: 1.67em; margin-bottom: 1.67em; font-size: 0.83em;
}
& & & & & h1 {
margin-top: 2.33em; margin-bottom: 2.33em; font-size: 0.67em;
}
}
固然,這個推薦樣式十分腦殘,編譯出來的結果會有 47KB 之巨,根本不可用,這裏只是借來演示一下。
除了 &,Sass 和 Stylus 更進一步,分別用 @at-root 和 / 符號做爲嵌套時「根」規則集的選擇器引用。這有什麼用呢?舉個例子,假設 HTML 結構是這樣的:
<article class="post">
<h1>我是一篇文章</h1>
<section>
<h1 class="section-title"><a href="#s1" class="section-link">#</a>我是章節標題</h1>
<p>我只是一個<em>例子</em>。</p>
</section>
</article>
若是我這麼寫 Sass 代碼,是徹底符合業務的嵌套關係的:
.post {
section {
.section-title {
color: #333;
.section-link {
color: #999;
}
}
/* other section styles */
}
/* other post styles */
}
可是這樣生成出來的選擇器會有 .post section .section-title .section-link,不少時候咱們以爲寫成 .post .section-link 就夠了。
因而咱們在 Stylus 中能夠這麼寫:
.post
section
.section-title
color #333
/.post .section-link
color #999
/* other section styles */
/* other post styles */
這樣輸出的 CSS 就會是:
.post section .section-title {
color: #333;
}
.post .section-link {
color: #999;
}
這就是咱們想要的樣子了。固然也能夠這樣寫:
.post
section
.section-title
color #333
/* other section styles */
.section-link
color #999
/* other post styles */
我我的是推薦這種寫法(不使用 root 引用)的,由於當你肯定 .section-link 的樣式不依賴於它位於 section 或 .section-title 下時,就不該該嵌套於此。不然若是爲了一點點性能上的考慮(還不必定會是優化),使得設計意圖變得更不許確,我以爲得不償失。
變量無疑爲 CSS 增長了一種有效的複用方式,減小了原來在 CSS 中沒法避免的重複「硬編碼」。
Less:
@red: #c00;
strong {
color: @red;
}
Sass:
$red: #c00;
strong {
color: $red;
}
Stylus:
red = #c00
strong
color: red
Less 的選擇有一個問題:@ 規則在 CSS 中能夠算是一種「原生」的擴展方式,變量名用 @ 開頭極可能會和之後的新 @ 規則衝突。(固然理論上只要 CSS 規範不引入 @a: b 這樣的規則,問題也不大。並且規範制定的時候也會參考不少現有的實現。)
相比之下 Sass 的選擇中規中矩,而 Stylus 就不一樣了,不須要額外的標誌符。這意味着:在 Stylus 中,咱們能夠覆寫 CSS 原生的屬性值!Stylus 的設計讓人有一種「你覺得你在寫 CSS,但其實你不是」的感受,後面會有更多這樣的例子。
順便說一下,CSS 規範也有關於變量實現的草案,目前的方案是這個樣子的:
/* global scope */
:root {
--red: #c00;
}
strong {
color: var(--red);
}
無論語法槽點如何,原生 CSS 變量能夠經過 DOM 結構來繼承,也就是說是代碼真正「運行」時(runtime)決定的。元素引用一個變量時會按 DOM 向上查找定義在上層元素上的同名變量。這一點是任何預處理語言都沒法作到的。能夠用 Firefox 31+ 看一下這個 demo。至於這種機制是否是好用,暫時還沒研究過。不過從開發的思惟慣性來看,還很難一會兒適應這種方式。
三種預處理器的變量做用域都是按嵌套的規則集劃分,而且在當前規則集下找不到對應變量時會逐級向上查找,注意這個和原生 CSS 的邏輯是徹底不一樣的。
若是咱們在代碼中重寫某個已經定義的變量的值,Less 的處理邏輯和其餘二者有很是關鍵的區別。在 Less 中,這個行爲被稱爲「懶加載(Lazy Loading)」。全部 Less 變量的計算,都是以這個變量最後一次被定義的值爲準。舉一個例子更容易說清楚:
Less:
@size: 10px;
.box {
width: @size;
}
@size: 20px;
.ball {
width: @size;
}
輸出:
.box {
width: 20px;
}
.ball {
width: 20px;
}
而在 Stylus 中:
size = 10px
.box
width: size
size = 20px
.ball
width: size
輸出:
.box {
width: 10px;
}
.ball {
width: 20px;
}
Sass 的處理方式和 Stylus 相同,變量值輸出時根據以前最近的一次定義計算。這其實表明了兩種理念:Less 更傾向接近 CSS 的聲明式,計算過程弱化調用時機;而 Sass 和 Stylus 更傾向於指令式。這兩種方式會致使怎樣的結果呢?
舉個例子來講,對於 Less,若是項目中引入了這樣一個文件:
@error-color: #c00;
@success-color: #0c0;
.error {
color: @error-color;
background-color: lighten(@error-color, 40%);
}
.success {
color: @success-color;
background-color: lighten(@success-color, 40%);
}
在業務代碼中,在不修改外部引入文件的狀況下,若是我想重寫這兩種狀態的配色,只須要從新配置 @error-color 和 @success-color 這兩個變量,就能改變 .error 和 .success 的樣式。
而在 Stylus 中,若是引入的第三方樣式庫中有這樣的代碼:
error-color = #c00
success-color = #0c0
.error
color: error-color
background-color: lighten(error-color, 40%)
.success
color: success-color
background-color: lighten(success-color, 40%)
這種狀況下後面的代碼就沒法經過重寫變量值來覆蓋樣式了。Sass 也是如此。優勢是 Stylus 和 Sass 這樣的處理會不容易受多個第三方庫變量名衝突的影響,由於一個變量不能影響在定義它之前的輸出樣式。
因爲 Sass 和 Stylus 變量在「運行」過程當中使用完能夠修改後再使用輸出不一樣的值,因此這二者還提供了「僅當變量不存在時才賦值」的功能:
Sass:
$x: 1;
$x: 5 !default;
$y: 3 !default;
// $x = 1, $y = 3
Stylus:
x = 1
x := 5 // or x ?= 5
y = 3
// x = 1, y = 3
由於變量只能在輸出前修改才能生效,因此若是要定製第三方庫的樣式,用戶代碼理論上得插入第三方庫的配置與樣式之間才能生效。而有了 !default,第三方庫在提供默認配置時能夠將開發給用戶修改的變量設置爲 !default,這樣只要用戶提早引入配置進行覆蓋,就能夠按需重寫默認配置了:
// lib.scss
$alert-color: red !default;
.alert {
color: $alert-color;
}
// var.scss
$alert-color: #c00;
// page.scss
@import var
@import lib
這樣最終頁面輸出的效果就是被用戶重定義過的內容了。
/* page.css */
.alert {
color: #c00;
}
因爲 Less 處理變量的方式,若是咱們要引入多個外部樣式庫或在多個團隊進行合做開發時,若是不能確保開發過程可控,那爲變量添加模塊前綴就變得頗有必要。
此外,Sass 中提供一個 !global 的語法來讓局部變量變成全局變量,也就是說 Sass 代碼能夠在內層覆蓋全局變量的值。輸出一段局部的樣式可能使得後續全部樣式都受到全局變量變化的影響。
預處理器都有定義變量的功能,除了在最多見的屬性值中使用,其餘還有哪些地方能用變量來加強對樣式的抽象、複用呢?
變量名插值
Less 中支持 @@foo 的形式引用變量,即該變量的名字是由 @foo 的值決定的。好比咱們能夠利用它簡化更清晰地調用 mixin:
// some icon font lib
// variables with prefix to prevent conflicts
@content-apple: "A";
@content-google: "G";
// clearer argument values
.icon-content(@icon) {
@var: ~"content-@{icon}";
&::before {
content: @@var;
}
}
.icon-apple {
.icon-content(apple); // "A"
}
.icon-google {
.icon-content(google); // "G"
}
選擇器插值
選擇器是樣式表和 DOM 的紐帶,是咱們實際暴露給 HTML 的接口。支持插值顯然可讓接口更不容易和其餘內容衝突。假設咱們在開發一個 UI 庫,生成的組件類名但願有一個可配置的前綴,這時選擇器插值就變得至關重要。初看下來,三者用法相似:
Less:
@prefix: ui;
.@{prefix}-button {
color: #333;
}
Sass:
$prefix: ui
.#{$prefix}-button
color: #333;
Stylus:
prefix = ui
.{prefix}-button
color #333
可是在 Less 中,有一個很嚴重的問題:經過選擇器插值生成的規則沒法被繼承(Extend dynamically generated selectors)!固然,若是有相似 Placeholder 的機制,這都不是事兒了。問題是 Less 沒有!將來的方案看來多是經過 :extend(.mixin()) 的方式實現相似功能(:extend mixins)。雖然用 :extend 自己的語法說不過去,可是在現有機制上來看還算能夠接受。關於樣式的繼承複用,後面會詳細講到。
@import 語句插值
Sass 中只能在使用 url() 表達式引入時進行變量插值:
$device: mobile;
@import url(styles.#{$device}.css);
Less 中能夠在字符串中進行插值:
@device: mobile;
@import "styles.@{device}.css";
Stylus 中在這裏插值無論用,可是能夠利用其字符串拼接的功能實現:
device = "mobile"
@import "styles." + device + ".css"
注意因爲 Less 的 Lazy Load 特性,即便是 @import 也是能夠在後面的文件內容中進行覆蓋的,修改掉變量就能夠在前面引入不一樣的外部文件。而 Sass 與 Stylus 一旦輸出語句,就沒法經過變量改變了。
屬性名插值
三個預處理器的目前版本都支持屬性名插值,用法也相似。這裏僅以 Stylus 爲例:
red-border(sides)
for side in sides
border-{side}-color: red // property name interpolation
.x
red-border(top right)
輸出:
.x {
border-top-color: #f00;
border-right-color: #f00;
}
其餘 @ 規則插值
三種預處理器均支持在 @media、@keyframes、@counter-style 等規則中進行插值。@media 插值主要用來作響應式的配置,而 @keyframes 這樣帶名稱名稱的 @ 規則則能夠經過插值來避免命名衝突。
Less:
@m: screen;
@orient: landscape;
@media @m and (orientation: @orient) {
body {
width: 960px;
}
}
@prefix: ui;
@keyframes ~"@{prefix}-fade-in" {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
Sass:
$m: screen;
$orient: landscape;
@media #{$m} and (orientation: $orient) {
body {
width: 1000px;
}
}
$prefix: ui;
@keyframes #{$prefix}-fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
Stylus:
m = screen
orient = landscape
mq = m + " and (orientation: " + orient + ")"
@media mq
body
width: 960px
vendors = official
prefix = ui;
@keyframes {prefix}-fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
三者均會輸出以下 CSS:
@media screen and (orientation: landscape) {
body {
width: 960px;
}
}
@keyframes ui-fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
Stylus 中彷佛有 and 時因爲表達式計算的邏輯不能直接像 Less 與 Sass 那樣寫插值,因此這裏採用了字符串拼接的方式。
@import
@import 對於模塊化開發來講很是有幫助,但就這個功能來講,三種預處理器的行爲各不相同。
先說 Less,Less 擴展了語法,爲 @import 增長了多種選項:
@import (less) somefile.ext
會將不管什麼擴展名的文件都做爲 Less 文件引入、一塊兒編譯;
@import (css) somefile.ext
直接編譯生成 @import somefile.ext,當作原生 @import;
@import (inline) somefile.ext
直接將外部文件拷貝進輸出文件的這個位置,但不會參與編譯;
@import (reference) somefile.ext
外部文件參與編譯,但不輸出內容,僅用來被本文件中的樣式繼承;
@import (optional) somefile.ext
引入文件但在文件不存在時不報錯,靜默失敗。
上面的選項是能夠聯合使用的,好比能夠這樣寫:
@import (less, optional) somefile.ext;
除此以外還有 once 和 multiple 選項分別用來表示去重和不去重的引入方式,默認爲 once。在不寫任何選項時,Less 會根據擴展名進行推斷來決定引入邏輯。
Sass 沒有擴展語法,而是本身推斷引入的方式。.css 後綴、絕對路徑、url() 表達式和帶有 media query 的 @import 會直接用原生 @import,其餘都會做爲 Sass 代碼參與編譯。相比之下 Less 更靈活也更復雜。Sass 有個特有的功能叫作「partial」,由於 Sass 默認的編譯工具能夠編譯整個目錄下的文件,因此當一些文件不須要編譯時,能夠在文件名前加上 _ 代表這是一個被別的模塊引入自己不須要編譯的代碼片斷。Less 的 lessc 因爲原本就只處理一個文件,因此這件事就交給用戶本身去寫編譯腳本了。Sass 中有一個比較棘手的問題是,@import 不會被去重,屢次引入會致使一個樣式文件被屢次輸出到編譯結果中。爲了解決這個問題,Foundation 作了以下的 hack:
// IMPORT ONCE
// We use this to prevent styles from being loaded multiple times for components that rely on other components.
$modules: () !default;
@mixin exports($name) {
// Import from global scope
$modules: $modules !global;
// Check if a module is already on the list
$module_index: index($modules, $name);
@if (($module_index == null) or ($module_index == false)) {
$modules: append($modules, $name) !global;
@content;
}
}
而後在定義樣式時都調用 exports 這個 mixin 來輸出,起到只輸出一次的效果。
Stylus 和 Sass 比較接近,也使用隱性推斷的方式,但在處理重複輸出的問題上,Stylus 給出了一個自定義指令 @require,用法和@import 徹底同樣,但只會輸出一次。Stylus 還支持通配符,好比 @import ‘product/*’ 會引入 product 目錄下的全部.styl 文件,但由於通常引入樣式都要顯式指定順序,因此這個功能實用性不高。
三者相比較之下,Sass 的引入功能彷佛有點殘缺,不能去重是很大的硬傷。雖然能用 Foundation 那種方式「解決」,但實際上這是語言自己應該解決的問題。
混入(mixin)應該說是預處理器最精髓的功能之一了。它提供了 CSS 缺失的最關鍵的東西:樣式層面的抽象。從語法上來講,三種預處理器的差別也比較大,這甚至會直接影響到咱們的開發方式。
Less 的混入有兩種方式:
直接在目標位置混入另外一個類樣式(輸出已經肯定,沒法使用參數);
定義一個不輸出的樣式片斷(能夠輸入參數),在目標位置輸出。(注:後面如無特殊說明,mixin 均用來指代此類混入。)
舉例來講:
.alert {
font-weight: 700;
}
.highlight(@color: red) {
font-size: 1.2em;
color: @color;
}
.heads-up {
.alert;
.highlight(red);
}
最後輸出:
.alert {
font-weight: 700;
}
.heads-up {
font-weight: 700;
font-size: 1.2em;
color: red;
}
能夠混入已有類樣式這一點很值得商榷。在上面的例子中,.alert 樣式在被混入時甚至能夠是 .alert();;.highlight() 混入時也能夠寫成 .highlight;。那麼咱們遇到這樣的代碼時根本不知道 alert 會不會是一個 HTML class。但因爲這一點是在 Less 還不支持 extend 時就有的,因此也可以理解做者可能就是將這做爲 extend 來用了。因此目前比較好的實踐是:用代碼規範規約開發者不得使用直接混入已有類樣式的方式,而是先定義 mixin 而後在輸出的類樣式中進行調用,調用時必須顯式加上 () 來代表這不是一個 class(事實上百度 EFE 已有的 Less 編碼規範就是這麼定義的)。繼承則應該直接經過 Less 的 :extend 來實現。
另外須要注意的是,Less 在進行混入時,會找到全部符合調用參數的「mixin 簽名」的樣式一塊兒輸出。好比:
.mixin(dark; @color) {
color: darken(@color, 10%);
}
.mixin(light; @color) {
color: lighten(@color, 10%);
}
.mixin(@_; @color) {
display: block;
}
@switch: light;
.class {
.mixin(@switch; #888);
}
這個例子中,第二個和第三個 mixin 都匹配了調用時的參數,因而它們的規則都會被輸出:
.class {
color: #a2a2a2;
display: block;
}
也就是說同名的 mixin 不是後面覆蓋前面,而是會累加輸出。只要參數符合定義,就會將 mixin 內部的樣式規則、甚至變量所有拷貝到目標做用域下。
這一點一樣會帶來一個問題:若是存在和 mixin 同名的 class 樣式,若是 mixin 沒有參數則在調用時會把對應的 class 樣式一塊兒輸出,這顯然是不符合預期的。
假設有個叫 .clearfix 的 mixin,有兩個 class 樣式調用了它(其中一個也叫 clearfix):
.clearfix() {
*zoom: 1;
&:before,
&:after {
display: table;
content: "";
}
}
.clearfix {
.clearfix();
}
.list {
.clearfix();
}
獲得的輸出是:
.clearfix {
*zoom: 1;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
.clearfix:after {
clear: both;
}
.list {
*zoom: 1;
}
.list:before,
.list:after {
display: table;
content: "";
}
.list:after {
clear: both;
}
.list:before,
.list:after {
display: table;
content: "";
}
.list:after {
clear: both;
}
.list 的樣式調用了兩次!這一點在開發中必定要注意,不要給和非輸出型 mixin 同名的類定義樣式。
對於 Sass,語義很是明確:
@mixin large-text {
font: {
family: Arial;
size: 20px;
weight: bold;
}
color: #ff0000;
}
.page-title {
@include large-text;
padding: 4px;
margin-top: 10px;
}
Sass 用 @mixin 和 @include 兩個指令清楚地描述了語義,不存在混入類樣式的狀況,可是書寫時略顯繁瑣一些。固然,用 Sass 語法 而非 SCSS 語法的話能夠簡單地用 = 定義 mixin,用 + 引入 mixin:
=large-text
font:
family: Arial
size: 20px
weight: bold
color: #ff0000
.page-title
+large-text
padding: 4px
margin-top: 10px
和 Less 不一樣,同名的 mixin 能夠覆蓋以前的定義,做用機制相似變量。
Stylus 和 Sass 相似,但不用什麼特殊的標記來引入:
border-radius(n)
-webkit-border-radius: n
-moz-border-radius: n
border-radius: n
.circle
border-radius(50%)
Stylus 中還有一個「透明 mixin」的功能,也就是說引入 mixin 徹底能夠和引入普通屬性同樣!例如上面的這個 mixin,也能夠這樣引入:
.circle
border-radius: 50%
這意味着能夠把兼容性上的處理隱藏在 mixin 中,直接用標準屬性同名的 mixin 按普通屬性的方式輸出。當不須要兼容老瀏覽器時,直接把 mixin 定義刪除仍然可以正常輸出。不過這種寫法雖然感受很是「爽快」,但要求開發者必須能很好地區分原生屬性和某個樣式庫中提供的 mixin 功能(對於有經驗的開發者問題不大),並且透明意味着看到一個普通屬性開發者不能判斷是否已經在某處用 mixin 進行了重寫,沒法明確知道這裏的代碼最後輸出會不會發生變化。在可控條件下,這個功能應該說是很是誘人的。
將聲明塊做爲混入參數
若是說調用時想傳入一組樣式聲明而非單個值,三種預處理器都提供了相應的功能,但實現方式各有不一樣。
在 Less 中須要先定義一個「規則集變量」(detached ruleset,其實就是 CSS 聲明塊,即規則集去掉選擇器的部分),而後在調用 mixin 時把它做爲參數傳進去,而後在 mixin 中用 @var() 的方式輸出:
.red(@custom) {
color: red;
@custom();
}
.alert {
@styles: {
font-weight: 700;
font-size: 1.5em;
}
.red(@styles);
}
在 Sass 和 Stylus 中,都支持直接在 mixin 調用下層傳入聲明塊:
Sass 下直接跟一個聲明塊便可,而後用關鍵字 @content 來進行輸出:
@mixin red() {
color: red;
@content;
}
.alert {
@include red() {
font-weight: 700;
font-size: 1.5em;
}
}
Stylus 支持兩種方法,首先是 Less 那樣的「具名」聲明塊,調用時當作變量:
red(foo)
color: red
{foo}
.alert
foo =
font-weight: 700
font-size: 1.5em
red(foo)
第二種是 Sass 那樣相似傳入「字面量」,而且用關鍵詞 block 輸出的方式。這種方式須要爲要傳入聲明塊的 mixin 前添加一個 +符號(多是來自 SCSS 的對應功能):
red()
color: red
{block}
.alert
+red()
font-weight: 700
font-size: 1.5em
第二種方式能夠看作是第一種方式的語法糖,在 mixin 只須要傳入一個聲明塊時能夠免去起名字帶來的困擾。
相比之下 Less 只支持先定義變量後傳入的方式,優勢是能夠傳入多個聲明塊;而 Sass 只支持傳入一個「匿名」聲明塊可是更簡單;Stylus 則是兩種方式都支持。這個功能在抽象「須要應用樣式的條件」時很是有用,好比咱們基於 Stylus 的樣式庫 rider 中就用它來實現對 media query 的抽象封裝。
混入很好用,可也有問題:若是多個地方都混入一樣的代碼,會形成輸出代碼的屢次重複。好比在 Stylus 下:
message()
padding: 10px
border: 1px solid #eee
.message
message()
.warning
message()
color: #e2e21e
會輸出:
.message {
padding: 10px;
border: 1px solid #eee;
}
.warning {
padding: 10px;
border: 1px solid #eee;
color: #e2e21e;
}
而咱們可能指望的輸出是:
.message,
.warning {
padding: 10px;
border: 1px solid #eee;
}
.warning {
color: #e2e21e;
}
也許你們會說能夠這麼寫:
message()
padding: 10px
border: 1px solid #eee
.message,
.warning
message()
.warning
color: #e2e21e
這樣就能夠按須要輸出了。但其實預處理器的一個好處就是能夠方便咱們進行模塊化開發。上面的例子中,.message 和 .warning的樣式若是是分佈在兩個模塊中的,我合併過的選擇器組樣式寫在哪裏呢?狀況更復雜的時候就更棘手了。
這個時候就該繼承出場了:
.message
padding: 10px
border: 1px solid #eee
.warning
@extend .message
color: #e2e21e
這樣就能夠按模塊進行開發(無論是分文件仍是在同一文件中按業務功能安排樣式的順序),同時兼顧輸出的效率了。
Stylus 的繼承方式來自 Sass,二者一模一樣。 而 Less 則又「獨樹一幟」地用僞類來描述繼承關係:
.message {
padding: 10px;
border: 1px solid #eee;
}
.warning {
&:extend(.message);
color: #e2e21e;
}
/* Or:
.warning:extend(.message) {
color: #e2e21e;
}
*/
同時,Less 默認只繼承父類自己的樣式,若是要同時繼承嵌套定義在父類做用域下的樣式,得使用關鍵字 all,好比&:extend(.message all);。
關於使用僞類描述繼承關係,Hax 在 Less 的另外一個 issue 下曾經言辭激烈地提出了批評,同時也遭到了 Less 項目組絕不客氣的迴應。我我的徹底贊同 Hax 的見解,由於選擇器是用來在樹結構中找到元素的,和樣式自己徹底無關。但 Less 社區在當時卻對這個語法表示了一致的贊同,不由讓人對其感到擔心。
無論語法如何,繼承功能還有一個潛在的問題:繼承會影響輸出的順序。假設有以下的 Sass 代碼:
.active {
color: red;
}
button.primary {
color: green;
}
button.active {
@extend .active;
}
而對應的 HTML 代碼是:
<button class="primary active">Submit</button>
很容易誤覺得效果是紅色的。而其實生成的 CSS 順序以下:
.active, button.active {
color: red;
}
button.primary {
color: green;
}
因爲合併選擇器的關係 .active 被移到了 .primary 以前,因此依賴順序而非選擇器 specificity 時可能會遇到陷阱。
Placeholder 是什麼?簡單來講就是一個聲明塊(預處理器 DSL 中的聲明塊,包含其下嵌套規則),可是不會在最終的 CSS 中輸出。其實這是一組「抽象」樣式,只存在於預處理器的編譯過程當中(相似 mixin),但不一樣之處是它能夠被繼承。這樣咱們就能夠在純樣式層爲聲明塊起與樣式強耦合的名稱而不怕它出如今 CSS 與 HTML 的「接口」——選擇器之中了。
Sass:
%red-card {
border: 1px solid #300;
background-color: #ecc;
color: #c00;
}
.alert {
@extend %red-card;
}
Stylus:
$red-card
border: 1px solid #300
background-color: #ecc
color: #c00
.alert
@extend $red-card
均輸出:
.alert {
border: 1px solid #300;
background-color: #ecc;
color: #c00;
}
Less 目前不支持這個功能,但開發組目前的共識是可能會用繼承 mixin 的方式來實現,好比上面的這個例子將來可能能夠經過以下方法實現:
.red-card() {
border: 1px solid #300;
background-color: #ecc;
color: #c00;
}
.alert {
&:extend(.red-card());
}
當前在 Less 下也有一個 hack 來模擬 placeholder 功能,原理是利用 @import (reference) 來實現「placeholder」不輸出的功能:
// placeholder.less
.red-card {
border: 1px solid #300;
background-color: #ecc;
color: #c00;
}
// style.less
@import (reference) "placeholder.less";
.alert {
&:extend(.red-card);
}
不過 @import (reference) 在複雜一些的狀況下(被引入的文件有 @import、有 :extend 等)可能會遇到一些 bug,好比:#185一、#187八、#1896。目前以 reference 方式引入 Bootstrap 時就會直接產生代碼輸出。
先說說原生函數。三種預處理器都自帶了諸如色彩處理、類型判斷、數值計算等內置函數,目前版本的數量都在 80 個左右。因爲 Sass 和 Stylus 都內置腳本語言,因此自帶函數中包括了不少處理不一樣數據類型、修改選擇器的函數。Sass 更是提供了很多特性檢測函數好比feature-exists(feature)、variable−exists(feature)、variable−exists(name) 等,這爲第三方庫的兼容性提供了很好的保障。由於有了這些函數能夠方便地對不一樣版本的 Sass 編譯器有針對性地提供兼容,而不怕在老版本的編譯環境中直接報錯。
三者調用函數的方式幾乎一致,不一樣之處在於 Sass 和 Stylus 支持直接指定參數名的方式傳入參數。以 Stylus 爲例:
subtract(a, b)
a - b
subtract(b: 10, a: 25) // same as substract(25, 10)
這樣作的好處是,若是參數列表比較長,Stylus 能夠直接爲列表後面的參數賦值,而不須要一路將以前的參數填上 null 或默認值。Stylus 將這個特性稱爲「Named parameters」,而 Sass 稱爲「Keyword arguments」。
關於函數,真正的區別在於:Sass 和 Stylus 都支持用 DSL 直接添加自定義函數,而 Less 中若是要添加自定義函數必須經過使用插件(2.0.0 之後的版本才支持插件)。這決定了用 Sass 和 Stylus 書寫的代碼可移植性更高,不須要編譯環境有插件便可運行,而 Less 則須要額外添加編譯時的依賴。
Sass 中自定義函數須要使用 @function 指令,並用 @return 指令返回結果:
@function golden-ratio($n) {
@return $n * 0.618;
}
.golden-box {
width: 200px;
height: golden-ratio(200px);
}
在 Stylus 中,這些都是隱含的,最後一個表達式的值會做爲返回值:
golden-ratio(n)
n * 0.618
.golden-box
width: 200px
height: golden-ratio(@width)
這種寫法和 mixin 有什麼區別?當把函數做爲 mixin 調用時,若是其中有 prop: value 這樣格式的內容,就會被當作樣式規則輸出。Stylus 中大量的內容都是根據調用時的 context 去隱式推斷該使用什麼邏輯進行輸出,而非 Less 和 Sass 那樣使用關鍵字去顯式地進行區分。
如下內容超出了微信字數限制,遂用圖片代替,體驗估計不怎麼好,建議點擊圖片放大查看