Sass 基本介紹

如果對本文有任何問題,建議,或者在前端技術體系方面有任何問題,可以新增我的微信: drylint , 我會盡可能為你解答,也會拉你進入前端技術進階交流群,大家一起進步~

Sass 是 CSS 的超集,支援所有 css 語法,並在其基礎上擴充套件。

Sass 支援像 css 一樣的大括號語法,副檔名為 .scss ,以及另一種使用縮排的語法,副檔名為 .sass

教程主要採取完全相容 css 的 SCSS 語法。

註釋(Comments)

支援兩種註釋,分別是:

  • 單行註釋 // 註釋文字
  • 多行註釋 /* 註釋文字 */

單行註釋(Single-line comments)

編譯的時候會直接被忽略,不會編譯到 CSS 中,所以也叫做“隱式註釋”(silent comments)。

// 註釋內容

多行註釋(Multi-line comments)

編譯時會將註釋編譯到 css 中,所以也叫做“顯式註釋”(loud comment)

// 這一行註釋不會出現在編譯的 css 中

/* 這一行會出現在編譯的 css 中,除非是在壓縮模式下則不會 */

/* 註釋中還可以包含插值:
* 1 + 1 = #{1 + 1} */ /*! 這行註釋即使在壓縮模式下也會編譯到 css 中 */ p /* 多行註釋可以寫在任何
* 允許空白出現的地方 */ .sans {
font-size: 16px;
}

編譯後的 css:

/* 這一行會出現在編譯的 css 中,除非是在壓縮模式下則不會 */

/* 註釋中還可以包含插值:
* 1 + 1 = 2 */ /*! 這行註釋即使在壓縮模式下也會編譯到 css 中 */
p .sans {
font-size: 16px;
}

SassDoc

文件註釋,類似於 jsdoc 。使用三斜線 /// 宣告。

/// Computes an exponent.
///
/// @param {number} $base
/// The number to multiply by itself.
/// @param {integer (unitless)} $exponent
/// The number of `$base`s to multiply together.
/// @return {number} `$base` to the power of `$exponent`.
@function pow($base, $exponent) {
$result: 1; @for $_ from 1 through $exponent {
$result: $result * $base;
} @return $result;
}

特殊的函式(Special Functions)

  • url()
  • xxx

url()

url() 函式在CSS中很常用,但是它的語法與其他函式不同,它可以接受帶引號的 url ,也可以接受不帶引號的 url。因為未加引號的 URL 不是有效的 SassScript 表示式,所以 Sass 需要特殊的邏輯來解析它。

如果 url() 的引數是一個有效的無引號的 url ,Sass 會原樣解析它,當然,插值也是可以用的。

如果引數不是一個有效的無引用的 url ,例如,如果它包含變數或函式呼叫,它將被解析為普通的 CSS 函式呼叫。

$roboto-font-path: "../fonts/roboto";

@font-face {
// This is parsed as a normal function call that takes a quoted string.
src: url("#{$roboto-font-path}/Roboto-Thin.woff2") format("woff2");
} @font-face {
// This is parsed as a normal function call that takes an arithmetic
// expression.
src: url($roboto-font-path + "/Roboto-Light.woff2") format("woff2");
} @font-face {
// This is parsed as an interpolated special function.
src: url(#{$roboto-font-path}/Roboto-Regular.woff2) format("woff2");
}

編譯後的 css :

@font-face {
src: url("../fonts/roboto/Roboto-Thin.woff2") format("woff2");
} @font-face {
src: url("../fonts/roboto/Roboto-Light.woff2") format("woff2");
} @font-face {
src: url(../fonts/roboto/Roboto-Regular.woff2) format("woff2");
}

calc()element()

calc()element() 函式是在 CSS 規範中定義的。因為 calc() 的數學表示式與 Sass 的演算法衝突,而 element() 的id可以被解析為顏色,所以它們需要特殊的解析。

Sass 允許任何文字出現在這些函式呼叫中,包括巢狀的圓括號。

除了可以使用插值來注入動態值會被編譯處理。其他任何東西都不會被解釋為 SassScript 表示式進行計算,而是原樣輸出。

progid:...()expression() 棄用

expression() 和以 progid: 開頭的函式是使用非標準語法的 Internet Explorer 遺留特性。儘管最近的瀏覽器已經不再支援它們,但是 Sass 繼續解析它們以實現向後相容。

min()max()

CSS在 CSS Values and Units Level 4 中增加了對 min()max() 函式的支援,Safari 很快就採用了它們來支援 iPhoneX 。

但是 Sass 在很久以前就已經有了自己的 min()max() 函式,為了向後相容所有現有的樣式表。這就需要額外的句法技巧來實現。

如果一個 min()max() 函式呼叫是有效的純 CSS ,它將被編譯為普通的 CSS 的 min()max() 函式呼叫。

"純CSS "包括巢狀呼叫 calc()env()var()min() ,或 max() ,以及插值。

但是,只要呼叫的時候包含了 SassScript 特性(如變數或函式呼叫),它就會被認為是對 Sass 自帶的 min()max() 函式的呼叫。

變數

在 Sass 中,宣告變數必須以 $ 開頭。

$red: #f00;

div {
color: $red;
}

編譯後的 css :

div {
color: #f00;
}

Sass 變數和 css 變數的區別:

  • Sass 變數會被編譯成真實的值然後輸出為 css ,也就是僅僅存在於開發階段。

  • CSS 變數對於不同的元素可以有不同的值,但是 Sass 變數一次只有一個值。

  • Sass 變數是不可逆的,這意味著如果您使用了一個變數,然後在後面更改了它的值,那麼之前的使用將依然保持不變。CSS 變數是宣告性的,這意味著如果在後面更改了值,它將影響前面的使用和以後的使用。

注意:和所有的 Sass 識別符號一樣,Sass 變數將連字元 - 和下劃線 _ 視為相同的字元。這意味著 $font-size$font_size 都指向同一個變數。這是 Sass 早期的歷史遺留,當時它只允許在識別符號名稱中使用下劃線。後來, Sass 增加了對連字元的支援,以匹配 CSS 的語法,sass 將這兩個字元視為等效處理,以便於使遷移更加容易。

預設值

比如開發一個庫,使用者可以選擇是否傳遞自定義的值,如果沒有傳遞則使用預設值。

為了實現這一點,Sass 提供了 !default 標誌。只有當變數沒有定義或者它的值為 null 時,才會給該變數賦值。否則,將使用預設的值。

配置模組變數

!default 定義的變數,可以在使用 @use 規則載入模組時配置。

在模組中宣告變數,並定義預設值:

// _library.scss

$black: #000 !default;
$border-radius: 0.25rem !default;
$box-shadow: 0 0.5rem 1rem rgba($black, 0.15) !default; code {
border-radius: $border-radius;
box-shadow: $box-shadow;
}

在引用模組時,選擇要自定義值的變數,忽略的變數則使用預設值:

// index.scss

@use 'library' with (
$black: #222,
$border-radius: 0.1rem
);

內建變數

內建模組定義的變數是無法被修改的。

比如,下面程式碼檢視修改內建變數,但不會成功:

@use "sass:math" as math;

// This assignment will fail.
math.$pi: 0;

作用域

在 css 檔案頂層宣告的變數是全域性變數,聲明後可以在模組中的任何地方被訪問。

在塊({})中宣告的變數是區域性變數,只能在宣告它們的塊內訪問。

// 全域性變數
$red: #f00; div {
// 區域性變數
$black: #000; color: $red;
} p {
// 在這裡引用區域性變數編譯時會報錯
color: $black;
}

當局部變數和全域性變數重名時,不會覆蓋全域性變數,而是同時存在,在哪個作用域訪問的就是哪個變數。

$red: #f00;

div {
$red: #f55; color: $red;
} p {
color: $red;
}

編譯後的 css :

div {
color: #f55;
} p {
color: #f00;
}

如果想用一個區域性變數去覆蓋全域性變數,也就是在塊中修改全域性變數的值,可以使用 !global 來完成:

$red: #f00;

div {
// !global 將修改全域性變數的值,而不是在塊中新建一個區域性作用域
$red: #f55 !global; color: $red;
} p {
color: $red;
}
div {
color: #f55;
} p {
color: #f55;
}

注意:如果使用 !global 的變數不是一個全域性變數,則編譯時會報錯。

在流程控制語句(@if/@each/@for/@while 等)中宣告的變數有一個自己的特殊作用域,它不會建立新變數去覆蓋同級作用域中的同名變數,而是簡單地進行原變數的賦值修改操作。

$dark-theme: true;
$red: #e55;
$black: #333; @if $dark-theme {
$red: #f00;
$black: #000;
} .button {
background-color: $red;
color: $black;
}

編譯後的 css :

.button {
background-color: #f00;
color: #000;
}

在流程控制語句中,賦值給已經存在的變數則是修改操作,如果是不存在的變數則會建立一個新的變數,但這個新的變數也只能在這個流程控制語句的作用域中使用。

檢測變數是否存在

Sass 核心庫提供了兩個用於處理變數的高階函式。meta.variable-exists() 函式返回給定名稱的變數是否在當前作用域中存在, meta.global-variable-exists() 函式做同樣的事情,但僅用於全域性作用域。

@debug meta.variable-exists("var1"); // false

$var1: value;

@debug meta.variable-exists("var1"); // true

h1 {
// $var2 is local.
$var2: value; @debug meta.variable-exists("var2"); // true
}
@debug meta.global-variable-exists("var1"); // false

$var1: value;

@debug meta.global-variable-exists("var1"); // true

h1 {
// $var2 is local.
$var2: value; @debug meta.global-variable-exists("var2"); // false
}

使用者有時可能會希望使用插值來定義基於另一個變數的變數名。Sass 不允許這樣做,因為它使得我們很難一眼就知道哪些變數在哪裡定義。但是,您可以做的是定義一個從名稱到值的 map,然後您可以使用變數訪問該對映。

@use "sass:map";

$theme-colors: (
"success": #28a745,
"info": #17a2b8,
"warning": #ffc107,
); .alert {
// Instead of $theme-color-#{warning}
background-color: map.get($theme-colors, "warning");
}

編譯後的 css :

.alert {
background-color: #ffc107;
}

插值(Interpolation)

插值幾乎可以在 Sass 樣式表的任何地方使用,以將 SassScript 表示式的結果嵌入到 CSS 塊中。

#{} 中放置一個表示式即可,比如可以用在:

  • 選擇器
  • 屬性名
  • 自定義屬性值
  • CSS 的 @ 語句中
  • @extends
  • CSS @imports
  • 字串
  • 特殊函式
  • CSS 函式名
  • 保留註釋(Loud comments ) /* ... */

下面展示部分用法,在選擇器,屬性,繼承,註釋語句中使用插值:

$selector: "hello";
$color: "color"; /* selector: #{$selector} */
.#{$selector} {
background-#{$color}: #f00;
} .#{$selector}-2 {
@extend .#{$selector}; border-#{$color}: #f00;
}
/* selector: hello */
.hello,
.hello-2 {
background-color: #f00;
} .hello-2 {
border-color: #f00;
}

在 SassScript 中,可以使用插值表示式將 SassScript 注入到未加引號的字串中。這在動態生成名稱(例如動畫)或使用斜槓分隔值時特別有用。

注意: SassScript 中的插值永遠返回一個未加引號的字串,在上面的例子中已經看到了。

插值對於將值注入到字串中很有用,但除此之外,在 SassScript 表示式中很少需要插值。

比如,使用變數完全不需要這樣寫: color: #{$red} ,而是可以直接使用變數: color: $red

注意:不應該使用插值插入數字。因為插值總是返回未加引號的字串,返回值並不能進一步用於計算,這也同時避免了違反 Sass 內建的的安全保護規則,以確保能夠正確使用單位。

Sass 有強大的單位運算,你可以使用它來代替。例如,與其寫 #{$width}px ,不如寫 $width * 1px,或者更好的是,以px開頭宣告$width變數。這樣,$width 已經有單位,你將得到一個很好的錯誤訊息,而不是編譯偽造的CSS。

還有,雖然可以使用插值將帶引號的字串轉換為不帶引號的字串,但使用 string.unquote() 函式會更清楚。所以應該用 string.unquote($string) 來代替 #{$string}

@語句(At-Rules)

Sass 在 CSS 之上添加了新的 @ 語句 :

  • @mixin@include 用於複用大的塊級樣式

  • @function 宣告自定義函式,用於 SassScript 表示式中

  • @extend 用於在一個選擇器中繼承另一個選擇器的樣式

  • @at-root 將程式碼塊內部的樣式編譯到 css 最外層(相當於頂級作用域)

  • @error 故意使編譯失敗而中斷,並丟擲錯誤資訊

  • @warn 丟擲一條錯誤資訊但不使編譯程式失敗而中斷

  • @debug 丟擲一條用於 debug 除錯的訊息

  • @if, @each, @for, @while 流程控制語句

@mixin and @include

@mixin 用於定義要複用的樣式塊,@include 用於呼叫這些樣式塊。

因歷史遺留原因,mixin 的名字和 Sass 識別符號一樣,連字元(hyphens) - 和下劃線(underscores)_ 被視為完全相同。

定義 mixin 的語法:

// 不需要傳引數時,複用固定的樣式程式碼
@mixin <name> {
// ...
} // 或 // 需要使用時傳遞引數,動態複用樣式程式碼
@mixin name(arg1, arg2, ..., argN) {
// ...
}

使用 mixin 的語法:

@include <name>;

// 或

@include <name>(arg1, arg2, ...)

使用示例:

// a.scss

@mixin input {
padding: 10px;
color: #333;
} @mixin button ($color, $fontSize) {
color: $color;
font-size: $fontSize;
}
@use "a";

.input {
@include a.input;
} .button {
@include a.button(#333, 20px);
}

編譯後的 css :

.input {
padding: 10px;
color: #333;
} .button {
color: #333;
font-size: 20px;
}

通常情況下,如果一個 mixin 定義時有多少個引數,那麼在呼叫時必須傳遞相同數量的引數,除非是定義 mixin 時使用了引數預設值。

mixin 引數預設值

定義一個引數預設值就像定義一個變數一樣,引數名後加一個冒號,然後就可以寫預設值了。

@mixin button ($color, $fontSize: 16px) {
color: $color;
font-size: $fontSize;
} .button {
@include button(#f00);
}

編譯後的 css :

.button {
color: #f00;
font-size: 16px;
}

預設引數值可以是任意 Sass 表示式,甚至是它前面的引數。

@mixin font ($size, $weight: if($size >= 24px, 600, 400)) {
font-size: $size;
font-weight: $weight;
} .div1 {
@include font(16px);
} .div2 {
@include font(24px);
}

編譯後的 css :

.div1 {
font-size: 16px;
font-weight: 400;
} .div2 {
font-size: 24px;
font-weight: 600;
}

關鍵詞傳參

預設情況下,呼叫 mixin 時傳遞的引數順序必須和定義時的引數一一對應。

如果傳遞引數時指定引數關鍵詞,則可以不按照定義的順序來傳參。

@mixin font ($weight, $size) {
font-size: $size;
font-weight: $weight;
} .div1 {
@include font($size: 16px, $weight: 500);
}

編譯後的 css :

.div1 {
font-size: 16px;
font-weight: 500;
}

注意,如果要傳遞不帶關鍵詞的引數,則它必須出現在關鍵詞引數之前。

任意數量的引數

如果 mixin 的最後一個引數名以 ... 結尾,那麼這個引數就可以接收傳遞過來的任意數量的引數,這個引數的值則會是一個列表。

@mixin order($height, $selectors...) {
@for $i from 0 to length($selectors) {
#{nth($selectors, $i + 1)} {
position: absolute;
height: $height;
margin-top: $i * $height;
}
}
} @include order(150px, "input.name", "input.address", "input.zip");

編譯後的 css :

input.name {
position: absolute;
height: 150px;
margin-top: 0;
} input.address {
position: absolute;
height: 150px;
margin-top: 150px;
} input.zip {
position: absolute;
height: 150px;
margin-top: 300px;
}

帶有關鍵字的任意引數

如果呼叫 mixin 帶了關鍵字,那麼任意引數需要使用 meta.keywords() 來處理,處理後將返回一個 map 型別的資料。

如果沒有將任意引數傳遞給 meta.keywords() 函式,那麼這個任意引數列表就不允許接收帶有關鍵詞的引數,編譯程式會報錯。

@use "sass:meta";

@mixin syntax-colors($args...) {
@debug meta.keywords($args);
// (string: #080, comment: #800, variable: #60b) @each $name, $color in meta.keywords($args) {
pre span.stx-#{$name} {
color: $color;
}
}
} @include syntax-colors(
$string: #080,
$comment: #800,
$variable: #60b,
)
pre span.stx-string {
color: #080;
} pre span.stx-comment {
color: #800;
} pre span.stx-variable {
color: #60b;
}

傳遞任意引數

接收的任意引數可以是一個列表(list),那麼,也可以把一個列表作為任意引數傳遞,同樣只需要在後面加上 ... 即可。

$font: 16px, 600, #f00;

@include font($font...);

同樣,也可以把一個 map 作為任意引數傳遞:

@mixin font ($size, $weight) {
font-size: $size;
font-weight: $weight;
} $font: (
weight: 600,
size: 16px,
); .div1 {
@include font($font...);
}

編譯後的 css :

.div1 {
font-size: 16px;
font-weight: 600;
}

@content 樣式塊

除了接受引數之外,mixin 還可以接受整個樣式塊,稱為內容塊。

在 mixin 中,在樣式塊中寫一個 @content 來宣告這個位置接受一個內容塊,傳遞一個樣式塊給 mixin,這個樣式塊的內容將會用來替換 @content

@mixin font ($size, $weight) {
font-size: $size;
font-weight: $weight;
@content;
} $font: (
weight: 600,
size: 16px,
); .div1 {
@include font($font...) {
font-family: sans-serif;
}
}

編譯後的 css :

.div1 {
font-size: 16px;
font-weight: 600;
font-family: sans-serif;
}

可以書寫多個 @content; ,這樣將會編譯生成多個接收到的樣式塊。

傳遞的樣式塊是有作用域限制的,只能訪問樣式塊所處的位置的變數,而不能去訪問 mixin 定義的作用域的變數。

如果要讓樣式塊使用 mixin 定義的作用域的變數,則需要通過 @content() 傳遞給樣式塊。

使用 `@content 時傳參

傳參使用 @content(arg1, arg2, ...) ,接收使用 @include <name> using ($arg1, $arg2, ...)

@mixin font ($size, $weight) {
font-size: $size;
font-weight: $weight;
@content(#f00, $size * 2);
} $font: (
weight: 600,
size: 16px,
); .div1 {
@include font($font...) using ($color, $margin) {
font-family: sans-serif;
color: $color;
margin: $margin;
}
}

編譯後的 css :

.div1 {
font-size: 16px;
font-weight: 600;
font-family: sans-serif;
color: #f00;
margin: 32px;
}

@content() 同樣可以傳遞 listmap 型別的引數,用法和前面一樣。

縮排語法的 mixin

縮排語法的 Sass 可以使用 = 來定義一個mixin,然後使用 + 來使用一個 mixin,但很不直觀,不建議使用。

@at-root

通常用於巢狀的選擇器中,在選擇器前寫下 @at-root 語句,用於將該選擇器編譯到樣式表的最外層,而不是巢狀所在的位置。

.div1 {
color: #f00;
.div2 {
color: #0f0; // 將 .div3 編譯到最外層
@at-root .div3 {
color: #00f;
}
}
}

編譯後的 css :

.div1 {
color: #f00;
}
.div1 .div2 {
color: #0f0;
}
.div3 {
color: #00f;
}

結合 mixin 來使用:

@use "sass:selector";

@mixin unify-parent($child) {
@at-root #{selector.unify(&, $child)} {
font-size: 16px;
@content;
}
} .wrapper .field {
@include unify-parent("input") {
color: #f00;
} @include unify-parent("select") {
color: #0f0;
}
}

編譯後的 css :

.wrapper input.field {
font-size: 16px;
color: #f00;
} .wrapper select.field {
font-size: 16px;
color: #0f0;
}

@at-root 還有另一種寫法 @at-root { ... }

.div1 {
font-size: 16px; @at-root {
.div2 {
color: #f00;
}
.div3 {
color: #0f0;
}
}
}

編譯後的 css :

.div1 {
font-size: 16px;
}
.div2 {
color: #f00;
} .div3 {
color: #0f0;
}

解決樣式之外的東西

預設情況下, @at-root 只會解決普通樣式規則, 其他像是 @media@supports 等將會被丟掉。

使用 @at-root (with: <rules...>) { ... }@at-root (without: <rules...>) 來告訴 Sass 在編譯的時候是否包括一些指定的規則。

除了合法的 @語句的名稱,如 @media 中的 media,還有兩個特殊的值可以在查詢中使用:

  • rule 指的是樣式規則。例如,@at-root (with: rule) 不保留 @ 語句,但保留樣式規則。
  • all 指所有 @語句 和 style 規則。
@media screen and (min-width: 900px) {
.page {
width: 100px; @at-root (with: media) {
/* @at-root (with: media) */
.div1 {
font-size: 16px;
}
} @at-root (without: media) {
.div2 {
/* @at-root (without: media) */
color: #111;
}
} @at-root (with: rule) {
.div3 {
/* @at-root (with: rule) */
color: #111;
}
} @at-root (without: rule) {
.div4 {
/* @at-root (without: rule) */
color: #111;
}
} @at-root (with: all) {
.div5 {
/* @at-root (with: all) */
color: #111;
}
} @at-root (without: all) {
.div6 {
/* @at-root (without: all) */
color: #111;
}
}
}
}

編譯後的 css :

@media screen and (min-width: 900px) {
.page {
width: 100px;
} /* @at-root (with: media) */
.div1 {
font-size: 16px;
}
}
.page .div2 {
/* @at-root (without: media) */
color: #111;
}
.page .div3 {
/* @at-root (with: rule) */
color: #111;
} @media screen and (min-width: 900px) {
.div4 {
/* @at-root (without: rule) */
color: #111;
}
} @media screen and (min-width: 900px) {
.page .div5 {
/* @at-root (with: all) */
color: #111;
}
}
.div6 {
/* @at-root (without: all) */
color: #111;
}