# 预编译器SASS

# 变量

变量定义与ES6 let类似, 必须先定义再使用, 且不能以数字开头, 可以包含字母、数字、下划线、中横线等

变量名中$border_color$border-color编译结果是一样的, 后写的会覆盖先写的, 建议使用中横线

# 局部变量

.container {
  $color: red;
}
.border {
  // 此处就无法使用局部变量
  border: $color;
}

# 全局变量

// 方式一  定义全局变量在括号外
$border-color: red;
.container {
  color: $border-color;
}

// 方式二  使用!global关键词
.container {
  $font-size: 16px !global;
}
.text {
  // 此处就可以使用了
  font-size: $font-size;
}

# 变量值类型

变量的值类型可以有如下几种:

  • 数字: 1、2、10px、30%
  • 字符串: 'foo'、'bar'
  • 颜色: blue、#1ab394、rgba(255, 255, 0, 0.6)
  • 布尔值: true、false
  • 空值: null
  • 数组(用空格或逗号作为分隔符): 1em, 2em, 3em 或者 1px 2px 3px
  • maps(类似JS的对象字面量): (k1: v1, k2: v2)
// 布尔值
$black-mode: true;
// 空值 通常由函数返回表示缺少结果
$var: null;
// 数组
$font-family: 'Open Sans', Helvetica, Sans-serif;
// maps
$color: (red: #fa0000, yellow: #fbe200, blue: #95d7eb);
// maps取值, 当取不到就是空值
.content {
  color: map-get($color, yellow);
  // 编译结果是空, 整条 background-color 都将不存在
  background-color: map-get($color, green);
}

# 默认值

$color: #333;
//如果$co1or之前没定义就使用如下的里默认值, 已定义就采用定义值
$color: #666 !default;
.container {
  border-color: $color;
}

# 混合和继承

SASS中, 复用CSS可以采用混合@mixin或者继承@extend, 但是两者编译结果是不一样的

# 混合语法

  • mixin是可以重复使用的组CSS声明
  • mixin有助于减少重复代码,只需声明一次,就可在文件中引用
  • 混合指令可以包含所有的CSS规则,绝大部分SASS规则,甚至通过参数功能引入变量,输出多样化的样式。
  • 使用参数时建议加上默认值
  • 内部还可以多级嵌套, 也可以是一个函数
// $left 有缺省值
@mixin flex-align($aitem, $left: 0) {
  -webkit-box-align: $aitem;
  -webkit-align-items: $aitem;
  -ms-flex-align: $aitem;
  align-items: $aitem;
  margin-left: $left
}
// 剩余参数扩展符
@mixin linear-gradients ($direction, $gradients...) {
  background-color: nth($gradients, 1);
  background-image: linear-gradients($direction, $gradients)
}

.container {
  // @include flex-align($aitem: center) 指定参数传参
  @include flex-align(center);
  // 传递多个参数
  @include linear-gradients(to right, red, orange, yellow);
}

# 继承语法

  • 定义基础类
  • 使用@extend来继承基础类
.alert {
  padding: 15px;
  font-size: 12px;
}

.important {
  border: 1px solid #ddd;
}

// 可以继承多个基础类
.alert-info {
  @extend .alert;
  @extend .important;
  color: #31708f;
}

.alert-success {
  @extend .alert;
  color: #3c763d;
}

# 一个案例

下面有一段CSS代码, 多个类都有一部分重复的代码, 如何采用@mixin@extend来解决复用重复的部分?

.base-warning {
  margin: 1px 0;
  font-size: 16px;
  opacity: 0.8;
  color: orange;
}

.base-danger {
  margin: 1px 0;
  font-size: 16px;
  opacity: 0.8;
  color: red;
}

.base-success {
  margin: 1px 0;
  font-size: 16px;
  opacity: 0.8;
  color: green;
}

使用SASS中的@mixin语法, 下面示例写法变得简单和优雅, 其实编译后的代码, 跟原来的CSS代码一模一样, 有大量的重复。

@mixin base {
  margin: 1px 0;
  font-size: 16px;
  opacity: 0.8;
}

.base-warning {
  @include base;
  color: orange;
}

.base-danger {
  @include base;
  color: red;
}

.base-success {
  @include base;
  color: green;
}

如此一来, CSS的体积就会增加, 从网络的优化角度来看, 这并不是一个完美的解决方案。其实, 此处可以使用@extend来解决重复

// 提取出公共部分为一个类
.base {
  margin: 1px 0;
  font-size: 16px;
  opacity: 0.8;
}

.base-warning {
  @extend .base;
  color: orange;
}

.base-danger {
  @extend .base;
  color: red;
}

.base-success {
  @extend .base;
  color: green;
}

编译结果如下:

// 这里会出现一个多余的类.base
.base, .base-warning, .base-danger, .base-success {
  margin: 1px 0;
  font-size: 16px;
  opacity: 0.8;
}
.base-warning {
  color: orange;
}

.base-success {
  color: green;
}

# 占位符

在上例中的混合继承示例中, 其实还能再度优化一点, 就是使用占位符选择器。上面的示例中的提取出的公共CSS类base, 在实际代码中并没有使用到, 但是编译结果中会出现一个这样的类.base。因此, 可以在基础的类.base定义处使用占位符%base, 编译结果就会去除掉多余的类.base

占位符选择器类似于类选择器,但是,它们不是以点(.)开头,而是以百分号(%)开头。 当在SCSS文件中使用占位符选择器时,它可以用于扩展其他选择器,但不会被编译成最终的CSS

%base {
  margin: 1px 0;
  font-size: 16px;
  opacity: 0.8;
}

.base-warning {
  @extend %base;
  color: orange;
}

.base-danger {
  @extend %base;
  color: red;
}

.base-success {
  @extend %base;
  color: green;
}

# 运算符

# 等号操作符

符号 说明
== 等于
!= 不等于
$theme: 1;
$light: true;
.container {
  @if $theme == 1 {
    background-color: red;
  }
  @else {
    background-color: blue;
  }

  @if $light == true {
    color: #fff;
  }
  @else {
    color: black;
  }
}

# 关系运行符

符号 说明
< (lt) 小于
> (gt) 大于
<= (lte) 小于等于
>= (gte) 大于等于
$theme: 3;
.container{
  // 也可以写成(@if $theme gte 5), 但是支持不好, 仍然建议使用符号
  @if $theme >= 5 {
    background-color:red;
  }
  @else{
    background-color:blue;
  }
}

# 逻辑运行符

符号 说明
and 逻辑与
or 逻辑或
not 逻辑非
$width: 100;
$height: 200;
$last: false;
$index: 5;
div {
  @if $width > 50 and $height < 300 {
    font-size: 16px;
  }
  @else {
    font-size: 14px;
  }

  @if not ($index > 5) {
    border-color: red;
  }
  @else {
    border-color: blue;
  }
}

# 数字操作符

符号 说明
+
-
*
/
% 取模

数字操作的通用规律如下:

  • %单位不能一起运算
  • 纯数字与(百分号单位运算)时会自动转化成相应的百分号单位值
  • 乘法运算, 两个相同单位无法运算, 会报错

除法比较特殊, 以下情况会被视作为除法运算符:

  • 如果值或值的一部分,是变量或者函数的返回值
  • 如果值被圆括号包裹
  • 如果值是算数表达式的一部分

如果需要使用变量,同时又要确保除法( / )不做除法运算而是完整地编译到CSS文件中,只需要用#{}插值语句将变量包裹

/* 加法运算 */
.content {
  width: 50 + 20; // 70
  width: 50 + 20%; // 70% 默认会添加单位
  width: 50% + 20%; // 70%
  width: 20 + 10px; // 30px
  width: 10px + 20px; // 30px
  width: 10px + 10pt; // 23.33333px 单位转换为px
  width: 20% + 10px; // 单位无法转换, 会报错
}

/* 减法运算 */
.content {
  width: 50 - 30; // 20
  width: 50 - 30%; // 20%
  width: 60% - 30%; // 30%
  width: 20px - 10px; // 10px
  width: 50px - 20pt; // 23.3333px
  width: 50px - 20; // 30px
  width: 50px - 18%; // 跟加法一样报错, 单位无法转换
}

/* 乘法运算 */
.content {
  width: 10 * 20; // 200
  width: 5 * 10%; // 50%
  width: 50% * 10%; // 报错, 出现两个%, 相同单位无法运算
  width: 10px * 20px; // 报错, 出现两个px, 相同单位无法运算
  width: 50% * 10px; // 报错, 单位无法转换
  width: 10pt * 10pt; // 报错
  width: 10pt * 10px; // 报错
  width: 10px * 2; // 20px
}

/* 除法运算 */
.content {
  $w: 100px;
  width: 10 / 5; // 不运算, 原样输出 10/5
  width: (10 / 5); // 2
  width: $w / 10; // 10px
  width: round($number: 50) / 2; // 25
  width: 10px / 2px + 3px; //  5 + 3px 结果为 8px
}

/* 取模运算 */
.content {
  width: 10 % 5; // 0
  width: 50 % 3px; // 2px
  width: 50px % 3px; // 2px
  width: 50% % 7; // 1%
  width: 50px % 7; // 1px
  width: 50% % 9%; // 5%
  width: 50% % 10px; // 报错, 百分号仍旧无法与px转换
  width: 50px % 10pt; // 10px
  width: 50px + 10pt; // 63.333px  10pt == 13.333px
}

# 字符串运算

+ 运算可用于连接字符串

注意

如果有引号字符串(位于+左侧)连接无引号字符串,运算结果是有引号的。相反,无引号字符串(位于+左侧)连接有引号字符串,运算结果则没有引 号。

如果两侧有一个值是函数返回的, 那么情况是不一样的, 无论引号''在左边还是在右边, 结果都是带引号''

.container {
  content: "foo" + bar;   // "foobar";
  content: foo + "bar";   // foobar;
  content: "foo" + "bar";  // "foobar";
  content: foo + bar;   // foobar;
  content: '' + selector-append('.a', '.b', '.c'); // '.a.b.c'
  content: selector-append('.a', '.b', '.c') + ''; // '.a.b.c'
}

# 插值语句

插值语句, 允许你在字符串中插入变量、运算和其他表达式的结果, 一般使用场景如下:

  • 选择器
  • 属性名
  • 属性值
  • 注释
$class-name: danger;
$attr: color;
$author: "lorain";
$font-size: 12px;
$line-height: 30px;
/*
  这是文件说明部分
  @author: #{$author}
*/
a.#{$class-name} {
  border-#{$attr}: #f00;
}

p {
  // 这种写法就不会运算, 使得结果为需要的联合值 12px / 30px
  font: #{$font-size} / #{$line-height} Helvetica;
}

# 内置模块

官网对内置模块 (opens new window)做了详细的介绍, 此处也不详细展开, 仅展示常用用法

# Color

SASS包含很多操作颜色的函数。例如:lighten()darken()函数可用于调亮或调暗颜色,opacify()函数使颜色透明度减少,transparent()函数使颜色透明度增加,mix()函数可用来混合两种颜色。

p {
  height:30px;
}

.p0 {
  background-color:#5c7a29;
}

.p1 {
  /*
    让颜色变亮
    lighten($color,Samount)
    $amount的取值在0%-100%之间
  */
  background-color: lighten(#5c7a29, 30%);
}
.p2{
  /*
    让颜色变暗 通常使用color.scale()代替该方案
  */
  background-color: darken(#5c7a29, 15%);
}
.p3{
  /*
    降低颜色透明度 通常使用color.sca1e()代替该方案
    background-color:opacify(#5c7a29, 0.5);
  */
  background-color: opacify(rgba(#5c7a29,0.1), 0.5);
}

# String

SASS有许多处理字符串的函数,比如向字符串添加引号的quote()、获取字符串长度的string-length()和将内容插入字符串给定位置的string-insert()

p {
  &:after {
    content:quote(这是里面的内容); // 给字符串添加引号
  }
  background-color: unquote($string: "#F00"); // 去掉字符串引号
  z-index: str-length("sass学习");
}

# Math

Math数值函数效处理数值计算,例如:percentage()将无单元的数值转换为百分比,round()将数字四舍五入为最接近的整数,min()max()获取几个数字中的最小值或最大值,random()返回一个随机数

// 使用前先导入
@use 'sass:math';

p {
  z-index: abs($number: -15);// 15
  z-index: ceil(5.8); // 6
  z-index: max(5, 1, 6, 8, 3); // 8
  opacity: random(); // 随机0-1
}

# List

List函数操作List,length()返回列表长度,nth()返回列表中的特定项,join()将两个列表连接在一起,append()在列表未尾添加一个值

p {
  z-index: length(12px); // 1
  z-index: length(12px 5px 8px); // 3
  z-index: index(a b c d, c); // 3
  padding: append(10px 20px, 30px); // 10px 20px 30px
  color: nth($list: red blue green, $n: 2); // blue
}

# Map

Map函数操作Mapmap-get()根据键值获取map中的对应值,map-merge()来将两个map合并成一个新的mapmap-values()映射中的所有值。

$font-sizes: ("small": 12px, "normal": 18px, "large": 24px);
$padding:(top:10px, right:20px, bottom:10px, left:30px);
p {
  font-size: map-get($font-sizes, "normal"); //18px
  @if map-has-key($padding, "right") {
      padding-right: map-get($padding, "right");
  }
  &:after {
      content: map-keys($font-sizes) + " "+ map-values($padding) + "";
  }
}

# Selector

Selector选择符相关函数可对CSS选择进行一些相应的操作,例如:selector-append()可以把一个选择符附加到另一个选择符,selector-unify()将两组选择器合成一个复合选择器。

.header {
  background-color: #000;
  content: selector-append(".a", ".b", ".c") + '';
  content: selector-unify("a", ".disabled") + '';
}

# 自检模块

自检相关函数,例如:feature-exists()检查当前SASS版本是否存在某个特性,variable-exists()检查当前作用域中是否存在某个变量,mixin-exists()检查某个mixin是否存在, 自检函数通常用在代码的调试上

$color: #F00;
@mixin padding($left: 0, $top: 0, $right: 0, $bottom: 0) {
  padding: $top $right $bottom $left;
}

.container {
  // 此处只能使用变量名, 不可以加$
  @if variable-exists(color) {
      color: $color;
  }
  @else {
      content: "$color不存在";
  }
  @if mixin-exists(padding) {
      @include padding($left: 10px, $right: 10px);
  }
}

# 流程控制

# @if

@if() 函数允许根据条件进行分支,并仅返回两种可能结果中的一种,可单独使用,也可配合混入、继承等其它语法。语法方式同 Javascriptif...else if...else

.container {
  // 第一种
  @if(/*条件*/){
    // ...
  }

  // 第二种
  @if(/*条件*/){
    // ...
  } @else {
    // ...
  }

  // 第三种
  @if(/*条件*/){
    // ...
  }@else if(){
    // ...
  }@else{
    // ...
  }
}

示例如下:

%triangle {
  width: 0px;
  height: 10px;
  display: inline-block;
}

@mixin triangle($direction:top,$size:38px,$border-color:black) {
  border-width: $size;
  border-#{$direction}-width: 0;
  @if ($direction == top){
    border-color: transparent transparent $border-color transparent;
    border-style: dashed dashed solid dashed;
  }
  @else if($direction == right){
    border-color: transparent transparent transparent $border-color;
    border-style: dashed dashed dashed solid;
  }
  @else if($direction == bottom){
    border-color: transparent $border-color transparent transparent;
    border-style: solid dashed dashed solid;
  }
  @else{
    border-color: dashed solid dashed dashed;
  }
}
.p1 {
  @extend %triangle;
  @include triangle(right, 20px, red);
}

上面的编译结果如下:

.p1 {
  width: 0px;
  height: 0px;
  display: inline-block;
}
.p1 {
  border-width: 20px;
  border-right-width: 0;
  border-color:transparent transparent transparent red;
  border-style: dashed dashed dashed solid;
}

# @for

@for指令可以在限制的范围内重复输出格式,每次按要求(变量的值)对输出结果做出变动。这个指令包含两种格式:

  • @for $var from <start> through <end>
  • @for $var from <start> to <end>

区别在于throughto的含义:

  • 当使用through时,条件范围包含<start><end>的值
  • 而使用to时条件范围只包含<start>的值不包含<end>的值
  • $var可以是任何变量,比如$i, 但<start><end>必须是整数值
// 从 1 到 3, 不包括 4
@for $i from 1 to 4 {
  .p#{$i} {
    width: 10px * $i;
    height: 30px;
    background-color: red;
  }
}

// 从 1 到 3, 包括 3
@for $i from 1 through 3 {
  .p#{$i} {
    width: 10px * $i;
    height: 30px;
    background-color: red;
  }
}

@for $i from 1 to 6 {
  #loading span:nth-child(#{$i}) {
    left: 20 * ($i - 1) + px;
    // animation-delay: 0.2 * ($i - 1) + s;
    animation-delay: unquote($string: '0.' + ($i - 1) * 2 + s);
  }
}

# @each

@each 指令 类似 Javascript 中的 foreach

指令语法

$var in <list>, $var可以是任何变量名,比如$length或者$name, 而<list>是连串的值,也就是值列表

$color-list: red green blue purple black;
@each $color in $color-list {
  // 取下标, List是从1开始的
  $index: index($color-list, $color);
  .p#{$index - 1} {
    background-color: $color;
  }
}

# @while

@while指令重复输出格式直到表达式返回结果为false, 这样可以实现比@for更复杂的循环

$column: 12;
@while $column > 0 {
  .col-sm-#{$column} {
    width: $column / 12 * 100%; // 方法一
    // width: $column / 12 * 100 + %; // 会报错, 不能采用这一种
    width: $column / 12 * 100#{'%'}; // 方法二, 百分号和数字间有一个空格, 如 33.333 %
    width: unquote($string: $column / 12 * 100 +'%'); // 方法三
  }
  $column: $column - 1;
}

# 函数

SASS 中, 函数的作用是将比较复杂或者常用的内容进行封装,便于复用。函数的语法定义如下

@function func-name([$param1, $param2, ...]){
  // ...
  @return $value;
}
  • 函数名 func-namefunc_name 效果相同
  • @return 它只允许在函数体中使用,并且每个@function必须以@return结束。 当遇到@return时, 它会立即结束函数并返回其结果
  • 函数入参可以设置默认值,也可以按参数名传值,或者通过...表示任意参数
@function background-linear-gradient($direction: to left, $start-color: red, $end-color: green, $gradients...) {
  @return linear-gradient($direction,$gradients);
}

div {
  // 正常传参
  background-image: background-linear-gradient(to right, blue, green);
  // 省略默认值
  background-image: background-linear-gradient(to right, blue);
  // 按照Key进行传参
  background-image: background-linear-gradient($start-color: red, $direction: to right);
}

$widths: 50px, 40px, 80px;
.top {
  // 使用...给函数传递列表
  width: min($widths...);
}

函数 @function 实现的某些功能混入@mixin 也能实现,但函数更侧重于做一些计算, 两者区别如下:

  • 混入mixin主要是通过传递参数的方式输出多样化的样式,为了可以现实代码复用
  • 函数的功能主要是通过传递参数后,经过函数内部的计算,最后@return输出一个值

# 三元条件函数

三元条件函数语法如下, 通过判断 $condition,如果条件成立,则返回 $if-true的结果,如果条件不成立,则返回 $if-false的结果

if($condition, $if-true, $if-false);
/* 流程控制语句@if */
$theme: "light";
.container {
  @if $theme == "light" {
    color: #000;
  } @else {
    color: #fff;
  }
}

/* 三元条件函数来改写 */
.container {
  color: if($theme == 'light', #000, #fff)
}

# 模块化

# @import

Sass拓展了@import的功能,允许其导入SCSSSASS文件。被导入的文件将合并编译到同一个CSS文件中,另外,被导入的文件中所包含的变量或者混合 指令@mixin都可以在导入的文件中使用。

例如 public.scss

$font-base-color: #333;

在index.scss里面使用

@import "public";
$color: #666;
.container {
  border-color:$color;
  color:$font-base-color;
}

注意

跟我们普通CSS里面@import的区别, 在原生的CSS中也有一个@import, 但是用法不一样。如果SCSS中使用原生方式, 不会把导入的模块当做SCSS处理, 编译后的代码是原封不动的保留源代码, 也无法引入其中的一些变量

以下几种方式,都将作为普通的CSS语句,不会导入任何SCSS文件

  1. 文件拓展名是.css
  2. 文件名以http://开头
  3. 文件名是url
  4. @import包含media queries
@import "public.css";
@import url(public);
@import "http://a.com/a.css";
@import 'landscape' screen and (orientation: landscape);

# @use

从其他SASS样式表加载mixin, function变量,并将来自多个样式表的CSS组合在一起,@use加载的样式表被称为模块,多次引入只包含一次

@use也可以看作是对@import的增强, 使用@use的每个文件都是一个模块, 都会有命名空间, 而@import是全局的, 后来的引入会覆盖前面的同名属性

语法

@use '<ur1>'[as alias | namespace]

主要特性:

  • 加载模块通过 @use 文件名 ,如 @use 'common'
  • @use引入同一个文件多次,不会重复引入,而@import会重复引入
  • @use引入的文件都是一个模块,默认以文件名作为模块名,可通过as aliasName取别名, 如 @use 'common' as c
  • @use引入多个文件时,每个文件都是单独的模块,相同变量名不会覆盖,通过模块名访问,而@import变量会被覆盖
  • @use方式可通过@use 'XXx' as * 来取消命名空间,建议不要这么做
  • @use模块内可通过$-$_来定义私有成员,也就是说以-开头_开头Variablesmixinsfunctions不会被引入
  • @use模块内变量可通过 !default 定义默认值,引入时可通用 with (...) 的方式修改,如 @use 'common' with ($font-size: 16px)
  • 可定义-index.scss_index.scss来合并多个scss文件,它@use默认加载文件
// global.scss
$_private-color: #1ab394; // 定义私有变量, 下划线和中划线效果一致, 都可以定义私有变量
$border-width: 2px !default; // 定义一个默认值
$colors: (red: #f00, yellow: yellow, green: green);

@mixin base {
  width: 100px;
}
@function get-properity($key: red){
  @return map-get($colors, $key)
}
// 默认命名空间为common
@use 'lib/common';
// 作为全局导入
@use 'lib/global' as *;
// 重命名模块名为g1
// 通过with修改global.scss内部定义的默认值
@use 'lib/global' as g1 with($border-width: 4px);

// 使用模块
.content {
  // 使用引入的mixin
  @include g1.base;
  color: g1.get-properity(yellow);
  font-size: g1.$font-size;
}

为了目录结构清晰, 一般会定义一个lib/index.scss, 用来专门引入其他各种模块, 使用时就不必在业务代码中写入一大篇@use 'xxx', 只需要使用@use 'lib/index', 甚至还可以省略为use 'lib'

# @forward

转发@forward可以加载一个模块的成员,并将这些成员当作自己的成员对外暴露出去,类似于类似于ES6export ... from, 通常用于跨多个文件组织SASS

上一步通过@use 'lib' 来统一导入多个SASS模块, 但也有对应的弊端, 里面的内部变量函数只能在导入处使用, 如果经过转发后, 就无法使用原模块的内容

例如:

// lib/index.scss
@use 'common';
@use 'global';
@use 'ui';

// 此处可以使用原来模块中的内容
$ifont-size: common.$font-size;
// 使用index.scss的模块 home.scss
@use 'lib' as index;

// 此处就无法使用lib中原来模块中的变量、函数、混入等等
$ifont-size: index.$font-size; // 报错

因此, 该种场景下就不得不使用@forward来转发原来模块, 并将原来的成员暴露出去, 也就是将lib/index.scss@use全部改写为@forward

# 全量转发

创建forward/common.scss

$font-size:14px !default;

* {
  margin: 0;
  padding: 0;
  font-size: $font-size;
  co1or: #333;
}

@function column-width($col, $total) {
  @return percentage($col / $total);
}

@mixin bgColor($bg-color: #f2f2f2) {
  background-color: $bg-color;
}

创建启动合并bootstrap.scss

// 包含其他很多模块, 统一转发
@forward 'forward/...';
@forward 'forward/global';
@forward 'forward/common';

使用

@use './bootstrap';
// 这里尽情使用原来common中的变量和各种内部模块

.content {
  font-size: bootstrap.$font-size;
  width: bootstrap.column-width(300, 1000);
  background: bootstrap.bgColor();
}

# 部分转发

选择性转发,通过 hide 隐藏元素,show 显示元素,如:

// bootstrap.scss

// 只隐藏bgColor
@forward 'forward/global' hide bgColor;
// 暴露$font-size 和 column-width
@forward 'forward/common' show $font-size, column-width;

# 命名冲突

如果两个模块中, 有同名的变量或者函数等, 通过@forward组合转发后, 在bootstrap.scss中会发生命名空间冲突, 因此需要使用到别名as 前缀-*来解决

例如: 两个模块forward/global.scssforward/common.scss, 都有同名变量$font-size

// forward/global.scss
$font-size: 20px;

@function column-width($col, $total) {
  @return percentage($col / $total);
}

@mixin bgColor($bg-color: #f2f2f2) {
  background-color: $bg-color;
}
// forward/common.scss
$font-size: 10px;
$color: green !default;

@function column-width($col, $total) {
  @return percentage($col / $total);
}

@mixin bgColor($bg-color: #f2f2f2) {
  background-color: $bg-color;
}

经过在forward/bootstrap.scss组合转发后再使用

// forward/bootstrap.scss

@forward 'forward/global';
@forward 'forward/common';

使用@use 'forward/bootstrap'就会报错, 因为两个同名的变量发生冲突了

所以, 需要在组合转发的时候加上as 前缀-*, 使用通过bootstrap.$c-font-sizebootstrap.$g-font-size

// forward/bootstrap.scss

@forward 'forward/global' as g-*;
@forward 'forward/common' as c-*;
@use 'forward/bootstrap';

p {
  font-size: bootstrap.$c-font-size;
  padding: bootstrap.$g-font-size;
  width: bootstrap.g-column-width(300, 1000);
  background: bootstrap.c-bgColor();
}

注意

使用 @forward 'forward/global' as g-*; 语法的时候, 如果后面有hide或者show来部分导出, 后面的部分变量也需要加上同样的前缀

例如: @forward 'forward/global' as g-* hide $g-font-size;

# 定义默认值

@forward语法同样支持with来修改变量默认值

// 此处with中不需要加前缀
@forward 'forward/common' as c-* hide $c-font-size with( $color: red );

其他文件用@use导入上面的模块, 同时修改默认值, 就需要给@forward的with加上!default, 然后

// bootstrap.scss
@forward 'forward/common' as c-* hide $c-font-size with($color: red !default);

// home.scss
// 此处需要加前缀
@use 'forward/bootstrap' with($c-color: yellow);

# 混用转发和导入

如果需要在转发的模块内部, 使用被转发模块的内部变量和数据, 可以通过 @use 引入使用

// 转发该模块供外部使用, 先使用@forward
@forward 'uses/common'as c-*hide c-bgColor with($font-size:60px !default);
@use 'uses/code';
// 内部使用模块的变量数据, 使用@use引入一次, 但是顺序必须在@forward后面
@use 'uses/common';

.block {
  background-color: code.$color;
  @include common.bgColor(#fff);
}

# 嵌套处理

# 连体符 &

连体符 &, 用于指代父选择器, 在一些特殊情况下, 也能跳出嵌套

// & 指代父选择器 .foo
// 可以看做 .foo .foo
.foo {
  & {
    font-size:12px;
  }
}
// 可以看做 .foo .foo .bar
.foo {
  & .bar {
    font-size:12px;
  }
}
// 可以看做 .foo .bar .foo
.foo {
  .bar & {
    font-size:12px;
  }
}

编译结果为:

.foo {
  font-size:12px;
}
.foo .bar {
  font-size:12px;
}
.bar .foo {
  font-size:12px;
}

与上个例子类似的还有BEM中的使用, 如下

// 第一种情况等同于第二种情况
.block {
  width: 1000px;

  &__element{
    font-size: 12px;
    &--modifier {
      font-size: 16px;
    }
  }

  &--modifier {
    font-size:14px;
  }
}

// 第二种情况
.block {
  width: 1000px;
  @at-root #{&}__element {
    font-size: 12px;
    @at-root #{&}--modifier {
      font-size: 16px;
    }
  }
  @at-root #{&}--modifier {
    font-size:14px;
  }
}

编译为:

.block {
  width: 1000px;
}
.block__element {
  font-size: 12px;
}
.block__element--modifier {
  font-size: 16px;
}
.block--modifier {
  font-size: 14px;
}

连体符 &相关的多种用法, 可以参考SASS中文网官方文章Sass中连体符(&)的运用 (opens new window)

还有一种特殊情况, 在媒体查询中, 该两种写法都是等同的

// 等同于第二种写法
.main {
  float: left;
  width: 45em;

  @media (max-width: 480px) {
    & {
        float: none;
        width: 100%;
    }
  }
}

.main {
  float: left;
  width: 45em;

  & {
    @media (max-width: 480px) {
        float: none;
        width: 100%;
    }
  }
}

两者都编译为:

.main {
  float: left;
  width: 45em;
}
@media (max-width: 480px) {
  .main {
    float: none;
    width: 100%;
  }
}

# @at-root

@at-root用来跳出嵌套,在多级嵌套时比较常用,包含withoutwith

//没有跳出
.parent-1 {
    color:#f00;
    .child {
        width:100px;
    }
}

//单个选择器跳出
.parent-2 {
    color:#f00;
    @at-root .child {
        width:200px;
    }
}

//多个选择器跳出
.parent-3 {
    background:#f00;
    @at-root {
        .child1 {
            width:300px;
        }
        .child2 {
            width:400px;
        }
    }
}

编译为

.parent-1 {
    color: #f00;
}
.parent-1 .child {
    width: 100px;
}

.parent-2 {
    color: #f00;
}
.child {
    width: 200px;
}

.parent-3 {
    background: #f00;
}
.child1 {
    width: 300px;
}

.child2 {
    width: 400px;
}

# without与with

默认@at-root只会跳出选择器嵌套,而不能跳出@media@support,如果要跳出这两种,则需使用@at-root (without: media)@at-root (without: support)@at-root的关键词有四个:

  • all 表示所有;
  • rule 表示常规css选择器;
  • media 表示media;
  • support表示support(@support主要是用于检测浏览器是否支持css的某个属性)

@at-root的默认值是@at-root (without:rule)

/*跳出父级元素嵌套*/
@media print {
  .parent1 {
    color:#f00;
    @at-root .child1 {
      width:200px;
    }
  }
}

/*跳出media嵌套,父级有效*/
@media print {
  .parent2 {
    color:#f00;
    @at-root (without: media) {
        .child2 {
            width:200px;
        }
    }
  }
}

/*跳出media和父级*/
@media print {
  .parent3 {
    color:#f00;
    @at-root (without: all) {
      .child3 {
          width:200px;
      }
    }
  }
}

@supports (display: flex){
  .parent {
    font-size: 14px;
    // 跳出supports, 不跳出父元素
    @at-root (without: supports){
      .child {
        font-size: 12px;
        .son {
            color: red;
        }
      }
    }
  }
}

@supports (display: flex){
  .parent {
    font-size: 14px;
   // 不跳出supports, 跳出父元素, 跟@at-root效果一样
    @at-root (with: supports){
      .child-1 {
        font-size: 12px;
        .son-1 {
            color: red;
        }
      }
    }
  }
}

编译成

/*跳出父级元素嵌套*/
@media print {
  .parent1 {
    color: #f00;
  }
  .child1 {
    width: 200px;
  }
}
/*跳出media嵌套,父级有效*/
@media print {
  .parent2 {
    color: #f00;
  }
}
.parent2 .child2 {
    width: 200px;
}
/*跳出media和父级*/
@media print {
  .parent3 {
    color: #f00;
  }
}
.child3 {
  width: 200px;
}

@supports (display: flex) {
  .parent {
    font-size: 14px;
  }
}
.parent .child {
  font-size: 12px;
}
.parent .child .son {
  color: red;
}

@supports (display: flex) {
  .parent {
    font-size: 14px;
  }
  .child-1 {
    font-size: 12px;
  }
  .child-1 .son-1 {
    color: red;
  }
}

# @at-root与 & 配合使用

.child{
    @at-root .parent &{
        color:#f00;
    }
}

编译成

.parent .child {
    color: #f00;
}

# @keyframe中使用

.demo {
    animation: motion 3s infinite;
    @at-root {
        @keyframes motion {
        }
    }
}

编译成

.demo {
    animation: motion 3s infinite;
}
@keyframes motion {}

# @at-root 高级用法

  • 结合@mixin使用, 可以复用代码, 不用多次分开书写.btn
@mixin dark-theme {
  @at-root .dark-theme & {
    // @content 类似于 vue 的 slot 插槽
    @content;
  }
}

.btn {
  background: transparent;

  &:hover {
    background: grey;
  }

  @include dark-theme {
    background-image: linear-gradient(cornflowerblue, rebeccapurple);
  }
}

编译结果

.btn {
  background: transparent;
}
.btn:hover {
  background: grey;
}
.dark-theme .btn {
  background-image: linear-gradient(cornflowerblue, rebeccapurple);
}
  • Vue transitionReact Transition Group 会设置一系列的类型名,如 .fade-enter.fade-exit,在 SASS 中我们可以直接拼接 &-enter 进行复用
@mixin dark-theme($modifiers...) {
  @if length($modifiers) > 0 {
    @each $modifier in $modifiers {
      @at-root .dark-theme &#{$modifier} {
        @content;
      }
    }
  } @else {
    @at-root .dark-theme & {
      @content;
    }
  }
}

.btn {
  background: transparent;

  &:hover {
    background: grey;
  }

  @include dark-theme {
    background: linear-gradient(cornflowerblue, rebeccapurple);
  }

  @include dark-theme(-enter) {
    background: cornflowerblue;
  }

  @include dark-theme(-enter-active, -exit) {
    background: rebeccapurple;
  }
}

编译为:

.btn {
  background: transparent;
}
.btn:hover {
  background: grey;
}
.dark-theme .btn {
  background: linear-gradient(cornflowerblue, rebeccapurple);
}

.dark-theme .btn-enter {
  background: cornflowerblue;
}

.dark-theme .btn-enter-active {
  background: rebeccapurple;
}

.dark-theme .btn-exit {
  background: rebeccapurple;
}

# 调试API

# @debug

@debug打印表达式的值,方便调试。

$font-sizes: 10px + 20px;
$style: (color: #bdc3c7);
.container {
    @debug $style;
    @debug $font-sizes;
}

# @error

@error显示致命错误

$colors: (
    blue: #c0392b,
    black: #2980b9
);

@function style-variation($style) {
  @error "Invalid color: '#{$style}'.";
  @if map-has-key($colors, $style) {
      @return map-get($colors, $style);
  }
}

.container {
  color: style-variation(white);
}

# @warn

@warn显示警告性建议,会显示堆栈信息。

$colors: (
  blue: #c0392b,
  black: #2980b9
);

@function style-variation($style) {
  @warn "Invalid color: '#{$style}'.";
  @if map-has-key($colors, $style) {
      @return map-get($colors, $style);
  }
}

.container {
  color: style-variation(white);
}

# 应用

# 主题切换

// 定义一个变量map
$themes: (
  light: (
    bgColor: #fff,
    textColor: #000,
    btnColor: #1ab394
  ),
  dark: (
    bgColor: #000,
    textColor: #ddd,
    btnColor: #133add
  )
);

// 定义一个 useTheme 混入
$currentTheme: light;
@mixin useTheme {
  @each $tk, $tv in $themes {
    $currentTheme: $tk !global;
    html[data-theme='#{$tk}'] & {
      @content;
    }
  }
}

// 定义一个取变量值函数
@function getVars($properity){
  $themeMap: map-get($themes, $currentTheme);
  @return map-get($themeMap, $properity);
}

// 实际使用
.home {
  @include useTheme {
    background-color: getVars('bgColor');
    color: getVars('textColor');
    .button {
      background: getVars('btnColor');
    }
  }
}

# 媒体查询

$adaptList: (
  phone: (320px, 480px),
  pad: (481px, 768px),
  notebook: (769px, 1024px),
  desktop: (1025px, 1200px),
  tv: 1201px,
);

@mixin useMediaQuery ($device) {
  $sizes: map-get($adaptList, $device);
  @if type-of($sizes) == 'list' {
    $min: nth($sizes, 1);
    $max: nth($sizes, 2);
    @media (min-width: $min) and (max-width: $max) {
      @content;
    }
  }
  @else {
    @media (min-width: $sizes) {
      @content;
    }
  }
}

// 使用方式
.header {
  color: #ddd;
  @include useMediaQuery('phone'){
    font-size: 12px;
  };
  @include useMediaQuery('pad'){
    font-size: 14px;
  };
  @include useMediaQuery('notebook'){
    font-size: 16px;
  };
  @include useMediaQuery('tv'){
    font-size: 20px;
  };
}

# 星空效果

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>SASS星空</title>
</head>
<body>
  <div class="layer1"></div>
</body>
</html>
// 星星生成函数
@function getShadows($n) {
  $shadows: '#{random(100)}vw #{random(100)}vh #fff';
  @for $i from 2 through $n {
    $shadows: '#{$shadows}, #{random(100)}vw #{random(100)}vh #fff';
  }
  @return unquote($shadows);
}

// 页面使用星星函数生成多个星星
.layer1 {
  // 1-100随机数
  // $size: unquote('#{random(100)}px');
  $size: 1px;
  $duration: 100s;
  position: fixed;
  width: $size;
  height: $size;
  border-radius: 50%;
  left: 0;
  top: 0;
  box-shadow: getShadows(1000);
  animation: moveUp $duration linear infinite;
  &::after {
    content: '';
    position:fixed;
    left: 0;
    top: 100vh;
    width: $size;
    height: $size;
    border-radius: inherit;
    box-shadow: inherit;
  }
}

@keyframes moveUp {
  100% {
    transform:translateY(-100vh);
  }
}

# 参考资料