你也許會不時地寫寫 Sass
玩玩,你也會很享受它帶給你各種便利。但還有一件事,你並不一定完全了解:插值
(interpolation) - 將一個占位符,替換成一個值。好了,你們都很幸運,因為今天我將把這種問題說清楚。
插值。啥玩意兒?
插值
,通常是指變量插值,或者變量替換。這不是Sass
獨有的。實際上,你可以在很多編程語言中,發現這種特性。比如 PHP
, Perl
, Ruby
, Tcl
, Groovy
, Unix shells
, 等等。我們經常說的是,插入一個變量,或者插入一個表達式。
我們還是先來看看一個例子吧。如果你有PHP的基礎,接下來應該會很容易了解。比如說,你想打印一個包含變量的字符串,最常見的方式:
$description = "awesome";
echo "Tuts+ is " . $description . "!";
這就不是插值的方式,你這是字符串的連接。這其中,連接了三個字符串:"Tuts+ is "
,awesome
(被 $description
所引用) 以及 "!"
。現在,我們就來看看使用插值而非字符串連接的方式:
$description = "awesome";
echo "Tuts+ is ${description}!";
包裹在變量兩邊的花括號會告訴PHP,用字符串打印出變量的值。值得注意的是:它需要在雙引號里面才可以工作(大多語言都是這樣)。
不管怎樣,這就是變量/表達式插值。是使用字符串連接,還是使用插值,這都取決於你自己。但可以說一點:插值其實就是字符串連接的語法糖而已。
Sass的插值
我們先看看在Sass中,變量替換是怎么工作。
Sass變量的命名,就像PHP一樣,都有着美元符號的前綴($
)。兩者的對比,顯得就結束了,因為談到插值,兩者的表現是不同的。有一個很好的解釋是:Sass是基於Ruby
的,它使用 ${}
進行表達式替換。
在Sass中,你會這樣做:
$description: "awesome";
@warn "Tuts+ is #{$description}!";
需要注意的是,變量中的$
不能像PHP一樣丟掉。變量被#{}
包裹了起來。另外值得一提的是:你可以插入任何類型的變量,不僅僅是字符串。
比如:
$answer: 42;
@warn "The Answer to the Ultimate Question of Life, the Universe, and Everything is #{$answer}.";
什么時候應該使用插值
現在你應該明白什么是插值了,也知道怎么在Sass中工作的了。是時候,開始着手實際場景了。首先,我們會再次使用剛才做過的@warn
指令,它將打印相應內容到控制台。
字符串 (Strings)
假設你有一組叫$colors
名的顏色映射(映射是指一個存儲了一系列 key/value 組合的變量),但你已經受夠了一次又一次地敲 map-get($colors, ...)
。所以,你寫了一個簡單的color()
函數,使用key去獲得相應的值。
把這些組合一下就是:
// _config.scss
$colors: (
"primary": tomato,
"secondary": hotpink
);
// _function.scss
@function color($key) {
@return map-get($colors, $key);
}
// _component.scss
.el {
background-color: color(primary);
}
一切都很好,不是嗎?現在,當你敲錯名稱,或者去取一個不存在的key時,你想給出錯誤的信息。這將通過 @warn
指令來完成。color()
函數如下:
@function color($key) {
@if not map-has-key($colors, $key) {
@warn "Key not found.";
}
@return map-get($colors, $key);
}
還不錯。如果你想知道哪個key沒有找到呢?
@function color($key) {
@if not map-has-key($colors, $key) {
@warn "Key `#{$key}` not found.";
}
@return map-get($colors, $key);
}
嘣~~變量插值。調用 color(awesomeness)
時,將會拋出以下信息:
Key
awesomeness
not found
這真的很棒,但我們卻不知道上下文是什么。為使后來的方便,我們可以將映射的名稱寫到錯誤信息里面。
@function color($key) {
@if not map-has-key($colors, $key) {
@warn "Key `#{$key}` not found in $colors map.";
}
@return map-get($colors, $key);
}
在這個場景里,由於這個 $colors
變量沒有使用插值,它將打印以下信息:
Key
awesomeness
not found in $colors map.
CSS函數 (Functions)
到目前為止,我們已經見到了最常見的變量替換場景:打印字符串中變量的內容。這確實是一個好的示例,但我覺得應該還有更好的場景:CSS函數中的變量,比如 calc()
。
假設你想基於側邊欄的寬度設置主容器的大小。你是一個勤奮的前端開發者,已經把這個寬度存儲在一個變量中,所以,你可能會這樣做:
$sidebar-width: 250px;
.main {
width: calc(100% - $sidebar-width);
}
然后,你會驚訝地發現,根本不work。沒有報錯,容器的大小卻又不正確。如果你去審查你的dom元素,你會看到這個 — 被划掉了。因為,這是不合法的。
.main {
width: calc(100% - $sidebar-width);
}
現在我們應該想到:calc()
是一個CSS函數,不是一個Sass函數。這就是說Sass會將整個表達式解釋成一個字符串。你可以試試:
$type-of-expression: type-of(calc(100% - $sidebar-width)); // string
因為這是一個字符串,難怪Sass表現和之前@warn
中的$colors
字符串一樣。$sidebar-width
被認為是一個常規字符串,所以打出來就是它自己。但這都不是我們所想要的,是吧?我們用插值這樣做。
.main {
width: calc(100% - #{$sidebar-width});
}
現在當Sass編譯這個樣式文件時,它會用#{$sidebar-width}
的值,250px替換#{$sidebar-width}
。最后,便是合法的CSS表達式。
.main {
width: calc(100% - 250px);
}
任務完成!我們僅僅在這里談了calc()
,但其實和其他CSS 原生函數是一樣的,包括偽類。比如:url()
,linear-gradient()
,radial-gradient()
,cubic-bezier()
。
以下是另一個使用CSS函數的例子:
@for $i from 1 through $max {
.el:nth-of-type(#{$i}) {
// ...
}
}
這是一個你有可能遇到過的場景:for
循環和:nth-*()
選擇器一起使用。再一次說明,你需要使用插值變量,才能最終得到想得到的結果。
小結:Sass會把CSS函數認為是字符串,所以想要在最后獲得它們的值,要求你轉義所有同它們一起使用的變量。
CSS指令 (Directives)
我們將視轉移到另一個有趣的變量插值場景:CSS指令,比如@support
,@page
,最重要的還是@media
。
現在,Sass是怎樣解析CSS指令,尤其是demia指令的。我已經查看過Sass的源碼,當我Ruby環境有點問題的時候,我找到一些有趣的事情。
def query_expr
interp = interpolation
return interp if interp
return unless tok(/\(/)
res = ['(']
ss
res << sass_script(:parse)
if tok(/:/)
res << ': '
ss
res << sass_script(:parse)
end
res << tok!(/\)/)
ss
res
end
第一行告訴Sass,如果有一個插值表達式的話,便返回media query
。如果找到一個開括號((
),便會一直往下走,解析所有的東西,反之就會拋出一個錯誤。我們在這里試試一個例子:
$value: screen;
@media $value {
// ...
}
毫不驚訝的是,這失敗了:
Invalid CSS after "@media ": expected media query (e.g. print, screen, print and screen), was "$value {"
就像錯誤信息提示的那樣,它期待一個media query。在這里,如果你的變量在 @media
字符串后面,需要使用插值才可以。比如:
$value: screen;
@media #{$value} {
// ...
}
和我們之前討論的Ruby轉義規則一樣,如果@media
后面緊跟(()
),你就不再需要插值變量了,因為Sass會求出所有在這些括號里面的值。比如:
$value: 1336px;
@media (max-width: $value) {
// ...
}
在這個示例中,Sass將這個表達式(max-width: $value)
轉化成(max-width: 1337px)
,最后生成合法的CSS結果。所以,我們便沒有必要再對變量轉義了。
選擇器 (Selectors)
好的,這將是最后一個示例:使用變量作為一個選擇器,或者選擇器的一部分。這不是一種常用的用法,以下的做法不太符合常識:
$value: custom;
selector-$value {
property: value;
}
不幸的是,這根本無法編譯成功:
Invalid CSS after "selector-": expected "{", was "$value {"
這是media query
那個例子的原因,差不多。Sass有着自己的方式解析一個CSS選擇器。如果它遇到了不可預知的東西,比如一個未經處理的美元符號,它就會直接崩潰。
幸運的,解決這個問題也相當簡單(你也已經知道怎么做了):沒錯,使用插值變量!
$value: custom;
selector-#{$value} {
property: value;
}
結語
在最后,Sass的插值沒有看起來那么簡單。在一些示例之中,你必須轉義你的變量,一些示例中,你卻不必。如果是那樣,你有兩種方式處理:
- 要么你便等着錯誤信息,然后轉義
- 要么你轉義除了常規CSS值(你清楚地知道,它會工作得很好)外的所有地方
不管怎樣,我希望你明白了插值變量是如何工作的。如果你有什么需要添加的,請在留言中指出。