Sass 之指令

292 阅读7分钟

Sass 指令大致有以下 11 种

  • @use:从其他样式表加载 mixins、函数、变量,并将不同的 css 样式表组合起来
  • @forward:将模块成员暴露出去
  • @import:导入 CSS、Sass文件
  • @mixin、@include:样式快的复用
  • @function:自定义可以在 SassScript 表达式中使用的函数
  • @extend:选择器相互继承样式
  • @at-root:可以用来放弃当前的嵌套层级,让其内部的 css 样式规则到根部
  • @error:使Sass 将会停止编译并打印错误
  • @warn:继续编译并打印警告
  • @debug:可以用来在开发样式表时查看变量或表达式的值,它会打印出该表达式的值,以及文件名和行号
  • @if、@each、@while:流控制。控制是否使用样式,以及使用样式的次数

如果 Sass 文件只打算作为模块加载,而不是自己编译,文件名以 _ 开头即可,这些被称为局部文件(partials),它们告诉 Sass 工具不要尝试自己编译这些文件。但是在导入这些模块时可以不用书写 _ 符号。

@use

使用 @use "<url>" 可以加载模块,以这种方式加载的任何样式时,只会在编译的 css 输出中包含一次

@use 的前面只能出现 @forward变量声明,其他的所有代码只能出现在 @use 之后。

// src/_corners.scss
$radius: 3px;
$-radius: 3px;

@mixin rounded {
  border-radius: $radius;
}

修改命名空间

@use "<url>" as <namespace> 可以修改默认情况下的 namespace,甚至可以通过 @use "<url>" as * 去除命名空间(不建议,容易发生名称冲突)。

//style1.scss
@use "src/corners" as C;

.button {
    @include C.rounded;
    padding: 5px + C.$radius;
}

/************************************************************************/

//style2.scss
@use "src/corners" as *;

.button {
  @include rounded;
  padding: 5px + $radius;
}

加载成员

在使用 @use <url> 加载文件之后,可以分别通过 <namespace>.<variable><namespace>.<function>()@include <namespace>.<mixin>() 使用文件中的变量、函数、mixin。默认情况下,namespace 是 URL 最后的一个组成成分。

// style2.scss
@use "src/corners";

.button {
  @include corners.rounded;
  padding: 5px + corners.$radius;
}

私有成员

如果不想将模块中的成员暴露给其他文件访问,将模块成员以 -_ 开头即可。

// style3.scss
@use "src/corners";

.button {
  @include corners.rounded;

  // 报错:模块的私有成员无法在模块外部使用
  padding: 5px + corners.$-radius;
}

变量可配置

加载变量有默认值的模块时,可以通过 @use <url> with (<variable>: <value>,<variable>: <value>) 的方式修改变量的默认值。值得注意的是:模块在加载之后,也可以修改变量的值。

//_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;
}

// style.scss
@use 'library' with (
  $black: #222,
  $border-radius: 0.1rem
);
// _library.scss
$color: red;

// _override.scss
@use 'library';
library.$color: blue;

// style.scss
@use 'library';
@use 'override';
@debug library.$color;  //=> blue

寻找模块

使用 @use 时,可以省略文件后缀名。

例如通过 @use "module" 使用模块时,不需要写扩展名,程序会自动查找:

  1. 查找 ./module.scss,没有则进行下一步
  2. 查找 ./module.sass,没有则进行下一步
  3. 查找 ./module.css,没有则进行下一步
  4. 查找 node_modules/module/sass/module.scss index文件:

如果在文件夹中有 _index.scss 或者 _index.sass ,则在加载该文件夹本身的URL时会自动加载索引文件。

@forward

使用 @forward "<url>" 可以把模块的成员暴露出去。

  • 通过 @forward 加载的模块成员,不能在当前文件内访问,假如需要访问,使用 @use
  • 当需要在一个文件中同时使用 @use 和 @forward 时,建议先写 @forward 再写 @use

添加前缀

通过 @forward "module" as xxx-* 可以给同一个模块中的成员统一添加前缀:

// src/_list.scss
@mixin reset {
  margin: 0;
  padding: 0;
  list-style: none;
}

// bootstrap.scss
@forward "src/list" as list-*;

// styles.scss
@use "bootstrap";
li {
  @include bootstrap.list-reset;
}

控制变量可见性

通过 @forward "<url>" hide <members...>@forward "<url>" show <members...> 可以控制哪些变量隐藏(可见)。

// src/_list.scss
$horizontal-list-gap: 2em;

@mixin list-reset {
  margin: 0;
  padding: 0;
  list-style: none;
}

@mixin list-horizontal {
  @include list-reset;

  li {
    display: inline-block;
    margin: {
      left: -2px;
      right: $horizontal-list-gap;
    }
  }
}

// bootstrap.scss
@forward "src/list" hide list-reset, $horizontal-list-gap;

配置变量

在转发模块时,可以对成员进行配置,修改默认值,或者指定一个确定的值均可。

// _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;
}

// _opinionated.scss
@forward 'library' with (
  $black: #222 !default,
  $border-radius: 0.1rem !default
);

// style.scss
@use 'opinionated' with ($black: #333);

@import

导入 Sass 或 CSS,使得文件中的 mixin、function、变量可访问,并且把样式联合起来。

与 CSS import 的区别

  • css 中的 @import 可以是一个线上 url 地址,浏览器会在运行时下载这个文件,而 sass 中的 @import 只能在编译打包阶段运行,所以在 sass 中只能导入一个本地存在的 sass/scss/css 文件;
  • scss 中允许写一个 @import 导入多个文件,文件以逗号 , 分隔开即可,css 中必须每个文件写一个 @import

在 sass 中导入 css 文件

  1. 编译时导入

    不要显示地写出扩展名 .css ,只写文件名即可

    /* a.css */
    .a {
      color: #f00;
    }
    
    // index.scss
    @import "a.css";
    .index {
      // 报错:The target selector was not found.
      // 目标选择器未找到
      @extend .a;
      font-size: 16px;
    }
    
  2. 运行时导入:

    表示让编译器原封不动地输出 @import 语句,而不是编译后替换掉它

    • .css 结尾

    • http://https:// 开头

    • 路径包含在 url() 之中

    • 语句中有媒体查询

      @import "xxx.css";
      @import "http://xxx.css";
      @import url(xxx);
      @import "landscape" screen and (orientation: landscape);
      

@import 中使用插值:sass 中的 @import 语句是不支持使用插值的,因为这可能会让人不知道变量、函数、mixin 是从哪里来的。但是,对于纯css @import 语句却是可以使用插值的,可以用来动态生成纯 css 的 @import 语句。

@mixin get-font($family) {
  @import url("http://xxx.com/#{$family}.css");
}

@include get-font("font-name");

@mixin and @include

@mixin 可以定义可重复使用的样式, @include 使用 @mixin 定义的样式。

@mixin 写法:@mixin <name> {...} 或者 @mixin <name> (<arguments>) {...} (带参数);

@include 写法:@include <name> 或者 @include <name> (<arguments>) (带参数),甚至可以是 @include <name> {...}

Mixin 名称和所有 Sass 标识符一样,将连字符和下划线视为相同。这意味着reset-listreset_list都指的是同一个 mixin。

传参方式

一般情况下,@include 调用 @mixin 时,传递的参数个数必须保持一致。

  1. 默认值传值

    通过定义默认值的形式,使该参数成为可选参数,如果未传递该参数,则将使用默认值

     @mixin replace-text($image, $x: 50%, $y: 50%) {
       text-indent: -99999em;
       overflow: hidden;
       text-align: left;
    
       background: {
         image: $image;
         repeat: no-repeat;
         position: $x $y;
       }
     }
    
     .mail-icon {
       @include replace-text(url("/images/mail.svg"), 0);
     }
    
  2. 名称传参

    当传递参数的时候,除了可以根据位置传参,还可以根据名称传参。这对于具有多个可选参数的 mixin 或 布尔 参数特别有用,因为这些参数在没有名称的情况下意义不明显。

    @mixin square($size, $radius: 0) {
      width: $size;
      height: $size;
    
      @if $radius != 0 {
        border-radius: $radius;
      }
    }
    
    .avatar {
      @include square(100px, $radius: 4px);
    }
    
  3. 参数列表

    如果 @mixin 声明中的最后一个参数以 ... 结尾,则该 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");
    

    任意数量参数时,也可以使用具名参数。通过 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,
    )
    

    同理,传递任意数量参数时,也可以使用 ... 传递参数列表。甚至可以利用这一特点为 mixin 定义别名。

    $form-selectors: "input.name", "input.address", "input.zip" !default;
    
    @include order(150px, $form-selectors...);
    
    
    @mixin btn($args...) {
      @warn "The btn() mixin is deprecated. Include button() instead.";
      @include button($args...);
    }
    

内容块

除了接收参数外,mixin 还可以接收整个样式块,称为内容块。内容块用 {} 传入,在 mixin 中代替 @content 内容。

@mixin hover {
  &:not([disabled]):hover {
    @content;
  }
}

.button {
  border: 1px solid black;
  @include hover {
    border-width: 2px;
  }
}

内容块可以通过 @content(<arguments>) 接收参数,并通过 @include <name> using (<arguments>) 传递参数

@mixin media($types...) {
  @each $type in $types {
    @media #{$type} {
      @content($type);
    }
  }
}

@include media(screen, print) using ($type) {
  h1 {
    font-size: 40px;
    @if $type == print {
      font-family: Calluna;
    }
  }
}

/************************************************************************/

// 编译后的 css
@media screen {
  h1 {
    font-size: 40px;
  }
}
@media print {
  h1 {
    font-size: 40px;
    font-family: Calluna;
  }
}

@function and @return

使用 @function <name>(arguments...) {...} 语法可以在 scss 中定义函数。

一般情况下使用函数时,传递的参数个数必须与定义函数的参数个数一致。接收参数时,可以分为以下两种情况:

  • 可选参数:通过定义参数默认值,使该参数称为可选参数变量名: SassScript 表达式),如果可选参数没有传值将使用默认值
  • 具名参数:使用名称传递参数
  • 参数列表:@function 声明中最后一个参数以 ... 结尾,则改函数的所有额外参数都作为列表传递给该参数

在传递参数时,可以传递一个参数列表 ... 作为函数调用的最后一个参数,它将被视为额外的位置参数。

$widths: 50px, 30px, 100px;
.micro {
  width: min($widths...);
}

/************************************************************************/

// 编译后的 css
.micro {
  width: 30px;
}

@return 只能出现在 @function 的函数体中,并且 @function 必须以 @return 结尾。

@extend

@extend <selctor> 可以让一个选择器继承另一个选择器的样式,这种继承会保证两个选择器就像完全一样,如:

.error:hover {
  background-color: #fee;
}
.error--serious {
  @extend .error;
  border-width: 3px;
}

/************************************************************************/

// 编译后的 css
.error:hover, .error--serious:hover {
  background-color: #fee;
}
.error--serious {
  border-width: 3px;
}
.content nav.sidebar {
  @extend .info;
}

// This won't be extended, because `p` is incompatible with `nav`.
p.info {
  background-color: #dee9fc;
}

// There's no way to know whether `<div class="guide">` will be inside or
// outside `<div class="content">`, so Sass generates both to be safe.
.guide .info {
  border: 1px solid rgba(#000, 0.8);
  border-radius: 2px;
}

// Sass knows that every element matching "main.content" also matches ".content"
// and avoids generating unnecessary interleaved selectors.
main.content .info {
  font-size: 0.8em;
}


/************************************************************************/

// 编译后的 css
p.info {
  background-color: #dee9fc;
}

.guide .info, .guide .content nav.sidebar, .content .guide nav.sidebar {
  border: 1px solid rgba(0, 0, 0, 0.8);
  border-radius: 2px;
}

main.content .info, main.content nav.sidebar {
  font-size: 0.8em;
}

占位符选择器

% 开始的选择器称为占位符选择器,它可以用 @extend 扩展。没有使用 @extend 对占位符选择器进行扩展时,它不会被编译到 css 输出当中;当使用 @extend 进行扩展时,会被编译到扩展它们的选择器中。

@extend 无法找到匹配的选择器时, sass 将会报错。如果只是想在 @extend 扩展选择器不存在的情况下不执行任何操作,只需在末尾添加 !optional 即可。

@error

在编写带有参数的mixin和函数时,通常希望这些参数符合要求(强制性)。此时就可以用到 @error <expression> 达到该目的。当执行 @error 语句时,Sass 将会停止编译并打印错误。

@mixin reflexive-position($property, $value) {
  @if $property != left and $property != right {
    @error "Property #{$property} must be either left or right.";
  }

  $left-value: if($property == right, initial, $value);
  $right-value: if($property == right, $value, initial);

  left: $left-value;
  right: $right-value;
  [dir=rtl] & {
    left: $right-value;
    right: $left-value;
  }
}

.sidebar {
  @include reflexive-position(top, 12px);
  //       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // Error: Property top must be either left or right.
}

@warn

在编写带有参数的mixin和函数时,通常希望这些参数符合要求(建议性)。此时就可以用到 @warn <expression> 达到该目的。当执行 @warn 语句时,Sass 会继续编译并打印警告。

$known-prefixes: webkit, moz, ms, o;

@mixin prefix($property, $value, $prefixes) {
  @each $prefix in $prefixes {
    @if not index($known-prefixes, $prefix) {
      @warn "Unknown prefix #{$prefix}.";
    }

    -#{$prefix}-#{$property}: $value;
  }
  #{$property}: $value;
}

.tilt {
  // Oops, we typo'd "webkit" as "wekbit"!
  @include prefix(transform, rotate(15deg), wekbit ms);
}

@debug

@debug <expression> 可以用来在开发样式表时查看变量或表达式的值,它会打印出该表达式的值,以及文件名和行号。

@at-root

@at-root 可以用来放弃当前的嵌套层级,让其内部的 css 样式规则到根部。例如:

.parent{
  color:red;
  @at-root .child {
    width:200px;
    height:50px;
  }
}


/************************************************************************/

// 编译后的 css
.parent {
  color: red;
}
.child {
  width: 200px;
  height: 50px; 
}

@at-root 默认情况下不会跳出 @media@supports 等指令。例如:

@media print { 
  @at-root {
    .foo { 
      color: green;
    } 
  }
}


/************************************************************************/

// 编译后的 css
@media print {
  .foo {
    color: green;
  }
}

如果想跳出指令,需要增加 without 语法。例如:

@media print {
    .page {
      width: 800px;
      a {
          color: red;
          @at-root(without: media) {
            span { color: #00f }
          }
      }
   }
}


/************************************************************************/

// 编译后的 css
@media print {
    .page {
        width: 800px;
    }
    .page a {
        color: red;
    }
}
.page a span {
    color: #00f;
}

可以看出 @at-root(without: media) 可以跳出 @media ,但是没有跳出父级选择器,如果我们想跳出 @media 和 父级嵌套,可以一次添加两个指令。

@media print {
  .page {
    width: 800px;
    a {
      color: red;
        // media rule 可以省略为 all
      @at-root (without: media rule) {
        span {
          color: #00f;
        }
      }
    }
  }
}


/************************************************************************/

// 编译后的 css
@media print {
    .page {
        width: 800px;
    }
    .page a {
        color: red;
    }
}

span {
    color: #00f;
}

withwithout 语法的关键词有以下可选值:

  • all:表示所有
  • rule:表示常规css
  • media:表示media
  • support:表示support

由此可以看出,默认的 @at-root 就是 @at-root(without:rule)

流程控制

@if 、 @else if 、 @else

@if <expression> {...}@else if <expression> {...}@else {...}

注意 @if <expression> {...}if(expression, value1, value2) 的区别

  • @if <expression> {...}:根据 expression 的真假决定大括号中的语句是否执行
  • if(expression, value1, value2):根据 expression 的真假决定取值,真值取 value1, 假值取 value2

sass 中为假值只有 falsenull,其他的如:空字符串、0 都为真值。

@each

遍历 List 写法: @each <variable> in <expression> {...}

$sizes: 40px, 50px, 80px;

@each $size in $sizes {
  .icon-#{$size} {
    font-size: $size;
    height: $size;
    width: $size;
  }
}

遍历 Map 写法: @each <key>,<value> in <expression> {...}

$icons: ("eye": "\f112", "start": "\f12e", "stop": "\f12f");

@each $name, $glyph in $icons {
  .icon-#{$name}:before {
    display: inline-block;
    font-family: "Icon Font";
    content: $glyph;
  }
}

解构写法: @each <variable1,variable2,variable3...> in <expression> {...} :每个变量的值为相应位置的值,如果相应位置没有值,则变量的值为 null。

$icons:
  "eye" "\f112" 12px,
  "start" "\f12e" 16px,
  "stop" "\f12f" 10px;

@each $name, $glyph, $size in $icons {
  .icon-#{$name}:before {
    display: inline-block;
    font-family: "Icon Font";
    content: $glyph;
    font-size: $size;
  }
}

@for

包含终止值:@for <variable> from <expression> through <expression> {...}

不包含终止值:@for <variable> from <expression> to <expression> {...}

$width: 5px;

@for $i from 1 to 3 {
  .box#{$i} {
    width: $width * $i;
  }
}

@for $i from 1 through 3 {
  .box#{$i} {
    width: $width * $i;
  }
}

@while

@while <expression> {...}

$whileVar: 1;
@while $whileVar < 3 {
  .box#{$whileVar} {
    width: $width * $whileVar;
  }
  $whileVar: $whileVar + 1;
}