『精』Sass 核心程序设计指南(这一篇就够了!)

8 篇文章 0 订阅

『精』Sass 核心程序设计指南(这一篇就够了!).jpg

『精』Sass 核心程序设计指南(这一篇就够了!)

文章目录


一、前言

Sass(英文全称:Syntactically Awesome Stylesheets),在前端领域中,Sass 作为 CSS 的预处理器,即使没使用过,大概率也或多或少听说过 Sass。
Sass 包括两套语法。最开始的语法叫做“缩进语法”,与 Haml 类似,使用缩进来区分代码块,并且用回车将不同规则分隔开。而较新的语法叫做“SCSS”,使用和 CSS 一样的块语法,即使用大括号将不同的规则分开,使用分号将具体的样式分开。通常情况下,这两套语法分别对应 .sass 和 .scss 两个文件扩展名。
**也可以将 Sass 说成一个语言,但这个语言没有自己的编译器或则是解释器,需要依赖其他语言进行编译,编译的最终结果还是 CSS。**目前主流版本为 Dart Sass,前期 Sass 主要依赖于 Ruby ,可以通过 Ruby 的 gem 包管理工具安装 Sass,大多数语言上都有社区支持封装,目前受到支持的语言类型可以在这里查看各自的文档 libsass,像NodeJs、Python、Java这些都是被支持的。
缩进语法已经停止维护,目前主流的语法是 “SCSS”,详情可以查看这里 ruby-sass-is-unsupported。对于前端来讲 Sass 主要通过 NodeJs 实现,且使用最新的 Scss 语法,本文主要以 Scss 语法为标准进行讲解,并以NodeJs做为宿主依赖。
图 1-1 Ruby Sass 已到生命的尽头

网上目前搜索 Sass 相关教程时可能会找到一些使用 Ruby Sass 的老版文章,如果你是一名使用 NodeJs 的前端程序员,请注意辨别。
CSS 预处理器语言中还存在 Less,这与 Sass 类似,在多数项目中使用率也不低,感兴趣的可以自行去了解 https://less.nodejs.cn/#


二、基本语句

2.1 变量声明

在 Sass 中声明一个变量只需要将值分配给以 $开通的变量名即可,同事变量存在各自的作用域,全局和局部(块级)作用域,不同局部作用域中的变量不能互相引用,这与大多数编程语言下的变量原则相同。

$color: #333;

.demo__bd {
  $color: #FCFCFC;
  @debug $color; // #FCFCFC
}

@debug $color; // #333

变量在声明或修改的时候,可以添加两种特殊标志,分别是 !default!global

  • !default 仅当该变量未定义或其值为 null 时,才会为该变量赋值,否则继续使用之前的值。这与 JavaScript 的逻辑空赋值 ??= 语法类似。
  • !global,添加了此标记的变量将始终分配给全局作用域,但不能声明全局变量,一般在局部作用域修改变量时候使用。不建议使用,容易造成维护以及阅读上的困难。
$color: #333;
$height: 100px;

.demo__bd {
  $color: #FCFCFC !default;
  $height: 88px !global;

  @debug $color; // #333
}

@debug $height; // #88px

2.2 数据类型

Sass 是一门基于样式的语言,由于样式的特殊性,Sass 拥有的几种的数据类型都会比较偏向于为编写样式而服务。

2.2.1 数字

数字由数字本身或携带单位的数字组成,不区分整数和浮点数,支持科学计数法。

// 数字类型
$num1: 123;
$num2: 88px;
$num3: 5.2e3; // 5200
2.2.2 字符串

字符串分两种情况,带引号的必定是字符串,不带引号的除了已有数据类型外将会转换为字符串。

// 字符串类型
$str1: "字符串";
$str2: 字符串;
$str3: --123;
$str4: "这是一个内容中带有\"引号\"的字符串";
$str5: \61; // 字母a的Unicode字符
$flex: flex;
$sm-prefix: ms;

.string {
  display: $flex;
  display: -#{$sm-prefix}-flex; // display: -ms-flex;
}

2.2.3 颜色

是的你没有看错,在 Sass 里颜色也是一个数据类型!
Sass 支持许多对于颜色的操作与扩展,详情请查阅 5.3 sass:color 颜色处理。

// #f2ece4
@debug #f2ece4; 
// rgba(179, 115, 153, 67%)
@debug #b37399aa; 
// #c69
@debug rgb(204, 102, 153); 
// rgba(107, 113, 127, 0.8)
@debug rgba(107, 113, 127, 0.8); 
// #dadbdf
@debug hsl(228, 7%, 86%); 
// rgb(225, 215, 210, 0.7)
@debug hsla(20, 20%, 85%, 0.7); 
2.2.4 列表/数组
  • 可以以空格或则逗号对列表中的元素进行分割,但不允许混用,否则以空格分割的元素将会被作为一个字符串整体。
  • 在编写声明语法时允许不携带传统的 [] 中括号,但对于单个元素的列表,中括号显得十分有用。
  • 与传统索引从 0 开始的语言相比,Sass 的列表索引是从 1 开始的,允许倒切索引,-1 指向最后一个元素。
  • Sass 的列表不能实现传统语言类似调用对象方法的操作,如想要对列表进行操作,想要用到一些内置函数或则 sass:list 扩展模块包,比如通过索引获取某个元素 nth(arr, 1)
$singleArr: [123];
$arr1: 1 2 3 4 5;
$arr2: 1, 2, 3, 4, 5, 1, 2, 2, 2;

// 索引从1开始
@debug nth($arr1, 1); // 1
@debug nth($arr1, -1); // 5

// 混用分割符号行为
$arr_confuse: 1 2, 3, 4 5, 1;
@debug nth($arr_confuse, 3); // 4 5

// 添加元素之前
@debug $singleArr; // [123]
// 添加元素
@debug append($singleArr, 1); // [123 1]
// 添加元素之后
@debug $singleArr; // [123]

2.2.5 布尔值

布尔值没什么好说的,就只有 true or false 两种。

2.2.6 Null

在 Sass 中的 null 是一个单例值,所有的null都是同一指向。

2.2.7 映射表

在映射表的示例代码中,用到了一些比较高级的语法,感兴趣的同学可以往下面看下去,分别是插值语法、循环语句以及BEM规范。

  • 类似于JavaScript的对象,Python的字典,但语法上不使用 {} 两个大括号将数值包裹。
  • 与列表一样,如想要对映射表进行操作,想要用到一些内置函数或则 sass:map 扩展模块包
@use "sass:map";

$fruits: (banana: yellow, orange: orange, durian: gold, apple: crimson);

// 不存在此元素则返回null
@debug map-get($fruits, "peal"); // null
@debug map.set($fruits, "peal", "khaki");
@debug map-get($fruits, "peal"); // null

.banana-box {
  background-color: map-get($fruits, "banana");
}

.durian-box {
  background-color: map-get($fruits, "durian");
}

.apple-box {
  background-color: map-get($fruits, "apple");
}

// 搭配BEM使用
$sizeDifference: (large: 50px, small: 30px, mini: 20px);

.demo__btn {
  @each $m, $size in $sizeDifference {
    &--#{$m} {
      width: $size;
      height: $size;
    }
  }
}

编译结果👇

.banana-box {
  background-color: yellow;
}

.durian-box {
  background-color: gold;
}

.apple-box {
  background-color: crimson;
}

.demo__btn--large {
  width: 50px;
  height: 50px;
}
.demo__btn--small {
  width: 30px;
  height: 30px;
}
.demo__btn--mini {
  width: 20px;
  height: 20px;
}

2.3 选择器嵌套🏆

嵌套可谓是 CSS 预处理语言最具有魅力的特点之一,这是组织样式类并使其更具可读性的好方法同时解决了传统 CSS 代码中编写选择器的繁琐性,在本文的示例代码中会大量使用到嵌套语法,在这里仅需要编写一个小小的示例,再通过与编译之后的 CSS 代码进行对比,你就能体会到嵌套的强大之处。

.demo__list {
  .demo__item {
    color: #FFF;

    .line {
      display: flex;
      justify-content: space-between;
      align-items: center;

      .left, .right {
        color: #FFF;
      }
    }

    .title {
      color: #FFF;
    }

    .desc {
      color: #FFF;
    }
  }
}

编译之后的结果是这样的👇:

.demo__list .demo__item {
  color: #FFF;
}
.demo__list .demo__item .line {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.demo__list .demo__item .line .left, .demo__list .demo__item .line .right {
  color: #FFF;
}
.demo__list .demo__item .title {
  color: #FFF;
}
.demo__list .demo__item .desc {
  color: #FFF;
}

2.4 流程控制语句与循环语句

Sass 中的流程控制语句由@if@else@else if这三部分组成,让我们先从一个简单的流程判断开始,将根据所设置的不同主题编译生成对应的 CSS。

$theme: "dark";

.demo__main {
  @if $theme == "dark" {
    &——dark {
      color: #FFF;
      background-color: #333;
    }
  } @else if $theme == "cute" {
    &--cute {
      color: #FF359A;
      background-color: #FFAAD5;
    }
  } @else {
    color: #333;
    background-color: #FFF;
  }
}

一般情况下流程控制会配合混入或则函数进行使用,根据调用时传入的参数判断是否需要某些样式或则影响计算的结果。

/// 页面安全容器
/// @param {string} $bgc [transparent] 背景颜色,默认transparent
/// @param {number} $around [30px] 边距大小,默认30px
/// @param {boolean} $padding [true] 是否启用边距,默认true
/// @param {Boolean} $safeBottom [true] 是否启用底部安全边距
@mixin container(
  $bgc: transparent,
  $around: 30rpx,
  $padding: true,
  $safeBottom: true
) {
  box-sizing: border-box;

  @if $bgc {
    background-color: $bgc;
  }

  @if $padding {
    padding: $around;
  }

  @if $safeBottom {
    @if $bottom {
      padding-bottom: calc(env(safe-area-inset-bottom, 0) + $around);
    } @else {
      padding-bottom: env(safe-area-inset-bottom, 0);
    }
  }
}

在 Sass 里有三种循环方法,与大多数语言一样,支持 @for@while 循环,除此之外还有一种专门处理可迭代数据类型的循环 @each

  • @while 没什么特别需要讲的,只需要满足判断条件为 true则这个循环会一直持续下去,适用于一些未知或不确定的条件判断场景。
  • Sass 里的 @for 是一种区间有序循环,简单来说就是在指定数字范围内获取索引,在 @for语句中如果需要排除最终数字使用 to,包含最终数字使用 through
  • @each 专门针对与可迭代数据类型,可以直接获取到元素的值,类似于 JavaScript 中的 for ... of ... 循环体。
$num: 0;
@while $num < 3 {
  $num: $num + 1;
  @debug "@while循环:#{$num}";
}

@for $i from 1 to 3 {
  @debug "@for ... from .. to .. 循环:#{$i}";
}

@for $i from 1 through 3 {
  @debug "@for ... from .. through .. 循环:#{$i}";
}

@each $item in ["a", "b", "c"] {
  @debug "@each循环:#{$item}";
}

通过循环,我们可以很方便的实现雪碧图/精灵图这一类的样式的编写,同时搭配上流程控制语句,从而提高代码的简洁率。

<div class="sprites">
  <div class="sprites__item">
    <div class="sprites__img"></div>
    <p>收藏</p>
  </div>
  <div class="sprites__item">
    <div class="sprites__img"></div>
    <p>画板</p>
  </div>
  <div class="sprites__item">
    <div class="sprites__img"></div>
    <p>城市</p>
  </div>
  <div class="sprites__item">
    <div class="sprites__img"></div>
    <p>历史记录</p>
  </div>
  <div class="sprites__item">
    <div class="sprites__img"></div>
    <p>模式</p>
  </div>
  <div class="sprites__item">
    <div class="sprites__img"></div>
    <p>喜欢</p>
  </div>
</div>
.sprites {
  $top: 0;
  $left: 6px;
  display: grid;
  grid-template: repeat(2, 1fr) / repeat(3, 30%);
  grid-row-gap: 20px;
  justify-content: space-between;
  width: 300px;
  height: 200px;

  &__item {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    align-items: center;
    position: relative;
    cursor: pointer;

    &:nth-last-of-type(-n + 3)::before {
      content: "";
      position: absolute;
      top: -5px;
      width: 80%;
      height: 2px;
      background-color: #F0F0F0;
    }

    &:not(:nth-of-type(3n))::after {
      content: "";
      position: absolute;
      top: 50%;
      right: -5px;
      width: 2px;
      height: 70%;
      background-color: #F0F0F0;
      transform: translateY(-50%);
    }

    &:hover {
      font-weight: bold;
      color: #2894FF;
    }

    // 通过循环为雪碧图Item快速定位
    @for $i from 1 through 6 {
      @if $i == 4 {
        $left: 3px;
        $top: -61px;
      }
      &:nth-of-type(#{$i}) {
        .sprites__img {
          background-position: $left $top;
        }
      }
      $left: $left - 80px;
    }
  }

  &__img {
    width: 80%;
    height: 70%;
    background: url("../images/sprites_demo.png") no-repeat 0 0 / 230px 120px;
  }
}

图 2-1 Sass 循环语句雪碧图
图 2-2 雪碧图原图

2.5 插值语句与父选择器别名

关于插值,只需要记住这一句话:插值几乎可以用在 Sass 样式表中的任何地方,当引入一个变量被当作字符串时,该用上差值,不管是之前的代码示例还是后面即将要讲解的函数混入,都可以用到。
也可以将插值理解成 Sass 特有的字符串格式化方法,使用 #{expression} 语法进行编写。
父选择器用于在嵌套选择器中用于引用外部选择器。

  • 第一个特点是重用外部的选择器,在需要更改类命名或则移植样式的情况下,只需要更改外部选择器的名称即可完成重用。
  • 第二个特点在于极大方便了对伪类和伪元素的编写。
/* 主题颜色 */
$primary-color: #2894FF;
$dark-primary-color: #0072E3;
// 全局变量
:root {
  --primary-color: #{$primary-color};
  --dark-primary-color: #{$dark-primary-color};
}

// 当作字符串格式化使用
$msg: "我是一段信息";
@debug "打印以下信息:#{msg}";

// 图标样式类
$icons: arrow, eyes, phone;
.icon {
  @each $name in $icons {
    &--#{$name} {
      // some styles
      background: url("./icons/#{$name}.svg") no-repeat 0 0;
    }
  }
}

.demo {
  // 等同于 .demo__hd
  &__hd {
    // 等同于 .demo__hd__title
    &__title {}
    // 等同于 .demo__hd__icon
    &__icon {}
  }

  // 等同于 .demo__hd
  &__bd {}
  // 等同于 .demo__line
  &__line {
    display: flex;
    justify-content: space-between;
    align-items: center;
    // 等同于 .demo__line__left, .demo__line__right
    &__left, &__right {}
  }

  // 悬浮效果
  &:hover {}
  // 点击效果
  &:active {}
}

2.6 函数与数字运算符

在 Sass 里,函数也可以是一种比较特殊的数据类型,比较适合定义复杂的操作且返回计算的结果,在声明函数时需要使用 @function 语法。
Sass 里能够在兼容单位的数值之间进行加减乘除等基础运算,值得注意的是,Sass 中的计算都是在编译阶段时处理,如果需要依靠浏览器动态计算,则需要编写原生的 CSS 语法。一种方法是使用 calc() 函数,此方法依靠 CSS 原生功能。

运算符/代替语法描述
+加法
-减法
*乘法运算
%求余
math.div(num1, num2)对于除法运算比较特殊,需要使用到 math 扩展模块的方法,或使用 calc()

在 Sass 中通过编写函数计算某个特定的结果是很常见的,需要用到函数与数值运算语法,咱们可以编写一个用于计算数值占据基数的百分比的辅助函数。

@use "sass:math";


/// 计算数值占据基数的百分比
/// @param {number} $val 将要被计算的目标值
/// @param {string} $base [100] 基数值,默认为100
/// @param {string} $round [2] 省略小数点后几位,默认为2
/// @return {string} 百分比计算值,带有%单位
/// @group xz-helper
/// @author XianZheCwx
@function percent($val, $base: 100, $round: 2) {
  $percentBase: pow(10, $round);
  @return "#{math.div(math.round(math.div($val, $base) * $base * $percentBase), $percentBase)}%"
}

@debug percent(48.566666); //48.57%

2.7 注释🍁

Sass 的注释与 JavaScript 一样,分单行、多行注释两种,语法上基本一致,但多行注释,也就是与 CSS 注释语法相同的会被编译到生成的 CSS 文件里。

// 使用此注释并不会被编译到CSS中

/* 此注释会被编译到CSS里 */

/**
* 此注释会被编译到CSS里
*/

在单行双斜杠的基础上,添加了三斜杠注释语法,此语法主要用于不想被编译到 CSS 的多行注释替换,且一些第三方工具会利用一些文档注释规范将它们生成漂亮的文档。在 SassDoc 工具里,会读取并解析注释中的文本解析成 Markdown 格式,并支持许多有用的注释声明来对其进行详细描述。

/// 文本溢出隐藏
/// @param {number} $line [1] 指定第几行隐藏
/// @param {string} $overflow [ellipsis] 溢出内容隐藏方式,默认ellipsis
/// @group xz-helper
/// @author XianZheCwx
@mixin textHidden($line: 1, $overflow: ellipsis) {
  overflow: hidden;
  text-overflow: $overflow;
  word-break: break-all;
  display: -webkit-box;
  -webkit-line-clamp: $line;
  -webkit-box-orient: vertical;
}

还有一种比较特殊的多行注释,在正常情况下启用压缩模式的 Sass 文件编译会将注释给剥离,使用 /*! 开头的多行注释会将其保留,避免一些重要的注释信息被剥离。编译生成的 CSS 文件里,!号会被保留。

/*! 这注释会保留在被压缩的CSS里 */
# 压缩命令
sass --style compressed .\sass\test.scss .\sass\test.css

三、样式复用🍒

大费周章将 CSS 变成 Sass,不就是为了提高开发效率降低维护成本么,而提高效率最直接显著的特点那就是支持将样式复用起来,这也是选择 Sass 这类样式预处理语言最主要的原因之一,本文主要从几个角度来讲讲如何复用样式。

3.1 函数

说到代码中的复用,函数和方法可谓是代码里的复用祖师爷,Sass 作为一门语言当然也支持函数。Sass 的函数没这么高级,只能返回一个值,存在一定的局限性,比较适用于需要复杂计算的场景

/// 制造一定存在的值,并确保附加单位
/// @param {number} $val 原始值
/// @param {number} $def [0] 若值为空时的替换值,默认为0
/// @param {string} $unit [px] 目标单位,默认为px
/// @return {number} 处理值
/// @group xz-helper
/// @author XianZheCwx
@function makeExistVal($val, $def: 0, $unit: px) {
  $last: $def;
  @if $val {
    $last: $val;
  }
  // 单位是否存在
  @if unitless($last) {
    $last: $last + $unit;
  }

  @return $last;
}

3.2 混入

但对于样式而言,重点在于其中的样式规则,函数更注重的是返回的结果和内部的处理过程,Sass 除了函数外还有一个利器,那就是混合Mixin。咋一看,Mixin能传参数与函数又几分相似,但内部结构却与函数截然不同。

// 不带括号与参数
@mixin container_1 {
  box-sizing: border-box;
  width: 100%;
  min-height: 100vh;
  padding: 20px;
}

// 参数与可选参数
@mixin container_2($bg, $minHeight: 100vh, $padding: 20px) {
  box-sizing: border-box;
  width: 100%;
  min-height: $minHeight;
  background: $bg;
  padding-bottom: $padding, $padding, calc(#{$padding} + env(safe-area-inset-bottom, 0))
}

.demo1 {
  @include container_1;
}

.demo2 {
  // 位置参数
  // @include container_2(#FFF, 97vh);
  // 关键字参数
  @include container_2(#FFF, $padding: 50px)
}

在这里,我提出一个问题:在 Sass,函数 VS 混入,到底哪个最厉害?你会去用哪个?
我相信大多数人都会选择后者,实际上也确实如此,混入所带来的语法便利性是函数所不能带来的。小孩才做选择,大人全都要!这句话我觉得在这里可以充分提现,为什么非要二选一,而不一起使用呢?大多数人都只用到了最基本的混合,但 Sass 的威力可不止如此,试着将混合、函数以及一些基本语法结合在一起,可以造出一个充满魅力类似辅助函数的趁手工具。

/// 制造一定存在的值,并确保附加单位
/// @param {number} $val 原始值
/// @param {number} $def [0] 若值为空时的替换值,默认为0
/// @param {string} $unit [px] 目标单位,默认为px
/// @return {number} 处理值
/// @group xz-helper
/// @author XianZheCwx
@function makeExistVal($val, $def: 0, $unit: px) {
  $last: $def;
  @if $val {
    $last: $val;
  }
  // 单位是否存在
  @if unitless($last) {
    $last: $last + $unit;
  }

  @return $last;
}

/// 页面安全容器
/// @param {number} $minHeight [98.2vh] 最小高度,默认98.2vh
/// @param {string} $bgc [transparent] 背景颜色,默认transparent
/// @param {number} $around [30px] 边距,默认30px
/// @param {number} $top [0] 顶部额外边距,默认0
/// @param {number} $bottom [36px] 底部额外边距,默认36px。
///         若为false,但启用安全边距和边距的情况下,使用边距作为下边距。
/// @param {boolean} $padding [true] 是否启用边距,默认true
/// @param {Boolean} $safeBottom [true] 是否启用底部安全边距
/// @group xz-helper
/// @author XianZheCwx
@mixin container(
  $minHeight: 97vh, $bgc: transparent,
  $around: 30px, $top: 0, $bottom: 36px,
  $padding: true, $safeBottom: true
) {
  box-sizing: border-box;

  @if $minHeight {
    min-height: $minHeight;
  }

  @if $bgc {
    background-color: $bgc;
  }

  @if $padding {
    padding: calc(
      #{makeExistVal($around)}
      + #{makeExistVal($top)}) $around makeExistVal($bottom);
  }

  @if $safeBottom {
    @if $bottom {
      padding-bottom: calc(#{$bottom} + env(safe-area-inset-bottom, 0));
    } @else if ($padding) {
      padding-bottom: calc(#{makeExistVal($around)} + env(safe-area-inset-bottom, 0));
    } @else {
      padding-bottom: env(safe-area-inset-bottom, 0);
    }
  }
}

.demo {
  @include container($minHeight: 100vh, $bottom: false);
}

编译之后的结果是这样的👇:

.demo {
  box-sizing: border-box;
  min-height: 100vh;
  background-color: transparent;
  padding: calc(30px + 0px) 30px 0px;
  padding-bottom: calc(30px + env(safe-area-inset-bottom, 0));
}

3.3 继承

Sass 的继承和传统语言的继承不一样,Sass 是专门为样式做处理,与混入相比,适合没有参数以及重复性较高的场景。对于通用性较高的弹性盒子样式,就可以考虑使用继承语法。

.flex-col {
  display: flex;
  flex-direction: column;
}

.flex-col-center {
  @extend .flex-col;
  justify-content: center;
  align-items: center;
}

.flex-col-icenter {
  @extend .flex-col;
  align-items: center;
}

.demo {
  @extend .flex-col-icenter;
}

但如果想做到继承一个公共基础的样式类,但又不想该样式类被编译成 CSS,类似其他语言面向对象语法中的抽象类时,就需要用到一个新的知识点:占位符选择器。 实际上也没那么复杂,就是替换为 % 符号,在最终生成的 CSS 文件里,以 % 开头的样式类不会被编译。

%flex-col {
  display: flex;
  flex-direction: column;
}

%flex-col-icenter {
  @extend %flex-col;
  align-items: center;
}

// 业务样式
.demo {
  @extend %flex-col-icenter;
}

3.4 变量

过分探究高级语法的同时,往往忽略最简单的方法,比如编写一个统一的背景色,高度等等。简单的东西不必复杂化,复杂的东西要简单化,对于一些简单的重复内容,其实用最基本的变量就可以解决,希望大家别追求语法的同时过分化,这样反而降低了效率。

$height: 100px;
$priamry-background: linear-gradient(#00E3E3, #02F78E);

.demo1 {
  height: $height;
  background: $priamry-background;
}

.demo2 {
  color: #000;
  height: $height;
  background: $priamry-background;
}

.demo3 {
  width: 100px;
  height: $height;
  background: $priamry-background;
}


四、特殊技巧与疑惑解答🎈

4.1 使用技巧

4.1.1 快速更改颜色透明值。

在 CSS 中原本的 rgba()rgb() 函数是不支持传入十六进制颜色值的,而在大多数设计稿且开发者的习惯上来讲,十六进制的使用还是占大多数。
对于这个问题 Sass 帮我们处理了,在 Sass 里能够直接使用 rgb() 函数对十六进制颜色进行透明度的控制,其返回值就是传统的 CSS rgba 颜色值。

// rgba(255, 255, 255, 0.2)
@debug rgb(#FFF, 20%);
// rgba(255, 255, 255, 0.8)
@debug rgb(#FFF, 80%);
// white
@debug rgb(#FFF, 100%);

小知识:rgba() 是旧版 CSS 语法,本质上是 rgb()的别名, 接受相同参数,作用相同,只要不是非常非常老版本的浏览器 rgb() 也能实现透明通道的效果。详情查阅:https://developer.mozilla.org/zh-CN/docs/Web/CSS/color_value/rgb

注意:此方法仅适用于 Dart Sass 版本,若是使用 Ruby Sass 或 LibSass 可能会存在兼容性问题。


4.1.2 终端调试输出

在编写一些需要用到计算的值时,难免会需要对计算的结果进行查看调试,大多数的做法是到浏览器上面的开发者工具里查阅,但其实也能够通过 Sass 自带的打印方法输出到控制台查阅,目前支持的打印方法有三种,分别是:@debug@warn@error@error 编写在 mixin 或则函数里,则会终止该作用域的继续执行,在全局作用域里则会终止编译。

@debug "debug级别打印";
@warn "warn级别打印";
@error "error级别打印";
4.1.3 判断数字类型是否携带符号

在某些混合或则函数封装里,如果想确保某个数字携带单位符号并应用到 CSS 属性上,那么就需要能够判断这个数字是否携带符号,通过内置函数 unitless 能够轻易实现此功能。

// 无符号
@debug unitless(888); // true
// 有符号
@debug unitless(888px); // false
@debug unitless(888pt); // false
4.1.4 配合 BEM 规范编写

在 CSS 语法规范领域,BEM规范与 Sass 搭配可谓是一件利器,感兴趣的可以移步:『精』CSS 小技巧之BEM规范

4.2 常见问题

  1. 本文中的是示例都是 scss 语法,那么如何在 NodeJs 中编译缩进语法呢?
    首先需要明白,这两种语法的文件后缀是不一样的,缩进语法是以 .sass 为后缀,而 scss 语法则是以 .scss 为后缀,正常情况下来讲,在 Sass 编译 的时候,会自动根据文件后缀来进行语法的选择编译,所以不管是哪个语法,尽管写即可,只需要保证所写代码与后缀名相匹配!

  2. node-sass 与 Sass 有什么区别?
    从实现上来看,node-sass 是应该基于 C/C++ 的 NodeJs 模块,本地需要存在对应的语言的编译环境和依赖,在编译速度上通常比纯 JavaScript 实现的 Sass 速度快很多。但 node-sass 的年纪并不小,官方也并不推荐使用,在 Sass 官网上对 libSass 弃用的具体原因做了说明,感兴趣的可以去看看 https://sass-lang.com/blog/libsass-is-deprecated/,node-sass 的官方库 https://github.com/sass/node-sass
    Sass 是一个纯 JavaScript 实现的 NodeJs 模块,且是 Dart Sass 的 JavaScript 版本,与 node-sass 相比,编译速度会慢很多,但它拥有最好的兼容性,不管是跨平台支持还是与最新的 NodeJs 版本兼容,Sass 是目前最推荐的编译模块,同时对于 Sass 的最新版本也会在此地方优先发布。同时在Webpack,Vite等前端构建工具上使用 Sass,需要搭配 sass-loader 辅助使用。

  3. Sass 扩展模块在引入时书写位置应该在哪?
    与大多数语言一样,对于模块与包的引入应放到代码文件开头,且在 Sass 里若不在开头书写将会抛出 Error: @use rules must be written before any other rules. 异常。


五、扩展模块包

Sass 将不常用的一些功能放到了模块包里,但内置的模块包数量并不算多,在这里放出几个比较常用的。此章节并不重要,可以简单将其当成速查手册,只需要知道有这个东西存在即可,需要使用到的时候再来查。

5.1 sass:math 数字计算

5.1.1 数学常数e 与 ∏
@use "sass:math";

@debug math.$e; // 2.7182818285
@debug math.$pi // 3.1415926536
5.1.2 最大与最小有限64位浮点数
@use "sass:math";

// 1797693134......
@debug math.$max-number;
// 由于Sass数字的10位精度,在许多情况下这将显示为0。
@debug math.$min-number; // 0
5.1.3 最大与最小整数
@use "sass:math";

// 9007199254740991
@debug math.$max-safe-integer
// -9007199254740991
@debug math.$min-safe-integer;
5.1.4 math.ceil($x) 向上取整 🥝
@use "sass:math";

// 向上取整
@debug math.ceil(4);   // 4
@debug math.ceil(4.2); // 5
@debug math.ceil(4.8); // 5
5.1.5 math.floor($x) 向下取整🥝
@use "sass:math";

// 向下取整
@debug math.floor(6);   // 6
@debug math.floor(6.2); // 6
@debug math.floor(6.8); // 6
5.1.6 math.clamp($min, $num, $max)
  • num 限制在 minmax 之间的范围,小于 min,则返回 min,大于 max,则返回 max
  • minnummax 三者单位不一定要相等,但必须兼容,或者都不带单位。对于反应一个真实的物理尺寸的绝对单位 in(英寸)、cm(厘米)、mm(毫米)之间是兼容的。
@use "sass:math";

@debug math.clamp(-1, 0, 1);          // 0
// 绝对单位
@debug math.clamp(-1in, 1cm, 10mm);   // 10mm
// 相对单位
@debug math.clamp(1px, -1px, 10px);   // 1px
@debug math.clamp(10%, 50%, 80%);     // 50%
5.1.7 math.max($args…) 最大值
  • 返回多个参数中的最大值。
@use "sass:math";

$numSets: 30px, 20px, 10px;
// 一般会以列表的形式传入
@debug math.max($numSets...);        // 30px
@debug math.max(100px, 50px, 10px);  // 100px
5.1.8 math.min($args…) 最小值
  • 返回多个参数中的最小值。
@use "sass:math";

$numSets: 30px, 20px, 10px;
// 一般会以列表的形式传入
@debug math.min($numSets...);        // 10px
@debug math.min(100px, 50px, 10px);  // 10px
5.1.9 math.round($x) 四舍五入 🍠
  • 此方法会将 x 四舍五入成整数,若要保留小数点后几位数值,需要进行换算。
@use "sass:math";

@debug math.round(6.2); // 6
@debug math.round(6.4); // 6
@debug math.round(6.6); // 7
// 保留小数点后两位 666.67
@debug math.div(math.round(666.666 * 100),  100);
// 不可用 / 作为除法运算符,这与 CSS 预留分隔符重复
// @debug math.round(666.666 * 100) /  100;
5.1.10 math.abs($x) 绝对值 🍗
@use "sass:math";

@debug math.abs(88px);    // 88px
@debug math.abs(-88px);   // 88px
5.1.11 math.compatible($num1, $num2) 单位兼容
  • 比较两个数值单位之间是否兼容,注意返回的是布尔类型。
@use "sass:math";

@debug math.compatible(6, 66);        // true
@debug math.compatible(8px, 88px);    // true
@debug math.compatible(8px, 88vh);    // false
5.1.12 math.is-unitless($x) 是否没有单位
  • 推荐使用替代内置函数 unitless,不需要引入 math 包。
@use "sass:math";

@debug math.is-unitless(88);      // true
@debug math.is-unitless(88px);    // false
5.1.13 math.div($num1, $num2) 🍆
  • 如果需要除法运算,此方法可以说是最实用的,一般情况下大多人都会想到以 / 作为运算符计算,但遗憾的是 / 符号在 CSS 中起到分隔符作用已被占用。
$width: 100px;
$offset: 2px;

@debug math.div(10, 3);  // 3.3333333333
@debug (math.div($width, 2) + $offset) * 2; // 104px
5.1.14 math.random($limit: null) 随机数🌺
  • 如果传入limt,则返回 1 到 limit 之间的随机整数,默认 limitnull 返回 0-1 之间的随机浮点数。
  • 值得注意的是,此随机数不是浏览器上的随机数,而是编译成 CSS 时生成的随机数,简单来说只有在不断变动的开发时才生效,生成的 CSS 文件里是固定的。
@use "sass:math";

// 0-1 之间随机数
@debug math.random();
// 1-100 之间随机整数
@debug math.random(100);
5.1.15 math.pow($x, $exponent) 平方计算
  • 计算 x 的 exponent 次幂。推荐使用替代内置函数 pow,不需要引入 math 包。
@use "sass:math";

// 2的2次幂:4
@debug math.pow(2, 2); 
// 2的4次幂:16,pow内置函数可替代math.pow
@debug pow(2, 4);

5.2 sass:string 字符串处理

对于字符串的扩展包下的方法,大部分有可以替代的内置函数,推荐直接使用这些内置函数而不用再引入 math:string扩展包。

5.2.1 string.insert($string, $insert, $index) 插入字符串
  • index 从 1 开始,而不是从 0 开始。
  • index 可以为负数,若为负数将从字符串最后一位开始往左算。
  • index 大于 string 的长度,则按照最大长度添加到末尾,小于 string 的负向长度时,则添加到开头。
  • 推荐使用替代内置函数 str-insert,不需要引入 string 包。
@use "sass:string";

// 《代码大全2》是一本非常不错的书
@debug string.insert("《》是一本非常不错的书", "代码大全2", 2);
// 在React Hook中,使用《useMemo》可以实现类似VUE计算属性的方法
@debug string.insert("在React Hook中,使用《》可以实现类似VUE计算属性的方法", "useMemo", -18);
// 用法与string.insert一致,推荐使用
// 前端框架有《Vue》,它是一个用于构建用户界面的渐进式JavaScript框架
@debug str-insert("前端框架有《》,它是一个用于构建用户界面的渐进式JavaScript框架", "Vue", 7)
5.2.2 string.length($s)
  • 推荐使用替代内置函数 str-length,不需要引入 string 包。
@use "sass:string";

$string: "React 是一个由 Facebook 开发和维护的 JavaScript 库。";
@debug string.length($string); // 40
// 用法与string.length一致,推荐使用
@debug str-length($string);    // 40
5.2.3 string.split($string, $separator, $limit: null) 分隔
  • stringseparator 作为分割符进行分割,limit为最大分割数,默认为 null 即不做限制,返回的是个列表。
@use "sass:string";

$colors: "#BEBEBE #FF5151 #FF95CA #FF8EFF #CA8EFF";
// ["#BEBEBE", "#FF5151", "#FF95CA", "#FF8EFF", "#CA8EFF"]
@debug string.split($colors, " ");
// ["#BEBEBE", "#FF5151", "#FF95CA #FF8EFF #CA8EFF"]
@debug string.split($colors, " ", 2);
5.2.4 string.slice($string, $start-at, $end-at: -1) 切片
  • 返回 string 的切片,从索引 start-at 开始,到索引 end-at 结束,end-at默认为 -1 代表到末尾结束。
  • 推荐使用替代内置函数 str-slice,不需要引入 string 包。
@use "sass:string";

@debug string.slice("这是React全家桶这是Vue全家桶", 11);
@debug string.slice("这是React全家桶这是Vue全家桶", 0, 10);
5.2.5 string.to-upper-case($s) ASCII 字母转换为大写
  • 更改某个字符串为大小写的场景并不多见,对于需要更改颜色的大小写以满足代码规范这类需要,建议使用代码格式化工具,而不是使用这类函数。
  • 推荐使用替代内置函数 to-upper-case,不需要引入 string 包。
@use "sass:string";

$color: "#3c3c3c";
@debug string.to-upper-case($color);   // #3C3C3C
@debug to-upper-case($color);          // #3C3C3C
5.2.6 string.to-lower-case($s) ASCII 字母转换为小写
  • 更改某个字符串为大小写的场景并不多见,对于需要更改颜色的大小写以满足代码规范这类需要,建议使用代码格式化工具,而不是使用这类函数。
  • 推荐使用替代内置函数 to-lower-case,不需要引入 string 包。
@use "sass:string";

$color: "#3C3C3C";
@debug string.to-lower-case($color);   // #3c3c3c
@debug to-lower-case($color);          // #3c3c3c

5.3 sass:color 颜色处理

对于颜色的可谓是有趣的,即看得见也摸得着,Sass 对于颜色的处理十分丰富,在需要的时候就能大大派上用场,提高样式的绘制效率。
Sass 对于处理颜色的方法繁多且部分功能重复,这里将根据其作用区分讲解,不像其他模块包以方法区分。

5.3.1 变浅与降低
  • desaturate($color, $amount):使 color 的饱和度降低,amount 介于 0% 和 100%(含)之间。
  • lighten($color, $amount): 使 color 更轻,amount 介于 0% 和 100%(含)之间。
<div class="demo-color">
    <button class="btn">按钮</button>
</div>
$primary-color: #1989FA;

.demo-color {
  display: flex;
  width: 100%;
  height: 100%;

  .btn {
    width: 100px;
    height: 40px;
    margin: auto;
    color: #FFF;
    border: 1px solid transparent;
    border-radius: 8px;
    background-color: $primary-color;
    cursor: pointer;

    // 悬浮效果变浅
    &:hover {
      background-color: lighten($primary-color, 5%)
    }
    
    // 点击效果饱和度降低
    &:active {
      background-color: desaturate($primary-color, 30%)
    }
  }
}

图 5-3-1 变浅与降低

5.3.2 变深与提高

编写文字叠加波浪动画,通过将颜色的改变可以衍生出更丰富的效果,需要注意的是在示例动画中采用了混合模式,需在容器为浅色系背景中使用。

  • darken($color, $amount):使 color 变暗,amount 介于 0% 和 100%(含)之间。
  • saturate($color, $amount):使 color 更加饱和,amount 介于 0% 和 100%(含)之间。
<div class="wave">
  <p>点赞啊!喂!</p>
  <div class="wave__bg1"></div>
  <div class="wave__bg2"></div>
  <div class="wave__bg3"></div>
</div>
$wave-color1: #46A3FF;
$wave-color2: #FF0000;
$wave-size: 800px;

@mixin waveSize($rate: 1.8) {
  width: $wave-size * $rate;
  height: $wave-size * $rate;
}

.wave {
    overflow: hidden;
    position: relative;
    width: $wave-size;
    font-size: 120px;
    font-weight: bold;
    text-align: center;
    color: #000;
    // 背景混合模式下必须为浅色系背景
    background-color: #FFF;

    &__bg1, &__bg2, &__bg3 {
      @include waveSize();

      position: absolute;
      top: -$wave-size * 1.68;
      left: 50%;
      z-index: 10;
      border-radius: 48% 52% 64% 36% / 34% 54% 46% 66%;
      transform: translateX(-50%);
      animation: waveAnimation 8s infinite linear;
      background-color: rgb($wave-color1, 85%);
      mix-blend-mode: lighten;
    }

    &__bg2 {
      @include waveSize(1.75);

      border-radius: 49% 51% 48% 52% / 53% 69% 31% 47% ;
      animation-duration: 18s;
      // 饱和且透明度为90%
      background-color: saturate(rgb($wave-color2, 90%), 20%);
    }

    &__bg3 {
      border-radius: 50% 48% 60% 40% / 36% 55% 44% 65%;
      animation-duration: 10s;
      // 变深且透明度为85%
      background-color: rgb(darken($wave-color1, 20%), 85%);
    }
  }

  @keyframes waveAnimation {
    0% {
      transform: translateX(-50%) rotate(0);
    }
    100% {
      transform: translate(-50%) rotate(360deg);
    }
  }

图 5-3-2 变深与提高

5.3.3 透明通道

透明通道的处理可以分为两种,一种是对含有透明度的颜色获取其 Alpha 值,另外一种则是对颜色的透明值进行更改处理。
对于获取 Alpha 值的场景较少,我能想到的场景是获取未知的颜色的透明值,将其用在其他如 opacity 的 CSS 属性上。

  • color.alpha($color):以 0 到 1 之间的数字形式返回指定颜色的 Alpha 通道。
    推荐使用替代内置函数alpha($color)
  • opacity($color):与 color.alpha 作用一致。
@use "sass:color";

// 0.32👇
@debug color.alpha($color1);
@debug alpha($color1);
@debug opacity($color1);

.foo {
  color: $color1;
  opacity: opacity($color1);
}

修改颜色的透明度,这是应用最多的场景,也是能体现 Sass 强大性的地方,可以做到简单的控制颜色的深浅度之类的操作,也能提高对于颜色处理的效率。

  • opacify($color, $amount):咋一看和 opacity 一样,但两个是不同的单词,要注意区分。使 color 随着 amount 增加 Alpha 通道数量,也就是更加不透明,amount 必须是介于 0 和 1(含)之间的数字。
  • fade-in($color, $amount):与 opacify作用一致。
  • transparentize($color, $amount):作用于 opacify 相反,使颜色更加透明。
  • fade-out($color, $amount):与 transparentize 作用一致。
@use "sass:color";

$color1: rgba(255, 255, 255, 0.66);
$color2: #FFF;
//  rgba(255, 255, 255, 0.88)
@debug opacify($color1, 0.22);
//  rgba(255, 255, 255, 0.88)
@debug fade-in($color1, 0.22);
// rgba(255, 255, 255, 0.6)
@debug transparentize($color1, 0.06);
// 十六进制颜色转换为:rgba(255, 255, 255, 0.88)
@debug transparentize($color2, 0.12);
// 十六进制颜色转换为:rgba(255, 255, 255, 0.66)
@debug fade-out($color2, 0.34);
5.3.4 混合颜色
  • color.adjust($color,$red: null, $green: null, $blue: null,$hue: null, $saturation: null, $lightness: null,$whiteness: null, $blackness: null,$alpha: null):将指定颜色一项或多项属性增加或减少固定量,如红蓝绿颜色通道、透明通道等。
    推荐使用替代内置函数adjust-color
  • color.change($color,$red: null, $green: null, $blue: null,$hue: null, $saturation: null, $lightness: null,$whiteness: null, $blackness: null,$alpha: null):与 color.adjust 类似,但不同的是 color.change是将颜色一项或多项属性替换为新值。
    推荐使用替代内置函数change-color
  • color.mix($color1, $color2, $weight: 50%):返回 color1color2 的混合颜色,$weight默认 50% 既两种颜色各取一半,数值越大取 color1 越多,数值越小取 color2 越多。
    推荐使用替代内置函数mix
@use "sass:color";

// rgba(200, 200, 200, 0.5)
@debug color.adjust(#000, $red: 200, $green: 200, $blue: 200, $alpha: -0.5);
// rgba(200, 200, 200, 0.5)
@debug adjust-color(#000, $red: 200, $green: 200, $blue: 200, $alpha: -0.5);
// 通道最大值为255,超过255则按照255算 ==> #9bffff
@debug adjust-color(#FFF, $red: -100, $green: 200, $blue: 200);
// rgba(196, 196, 196, 0.2)
@debug color.change(#000, $red: 196, $green: 196, $blue: 196, $alpha: 0.2);
// rgba(196, 196, 196, 0.2)
@debug change-color(#000, $red: 196, $green: 196, $blue: 196, $alpha: 0.2);
// #c4c4c4
@debug change-color(#000, $red: 196, $green: 196, $blue: 196);
// #cccccc
@debug color.mix(#FFF, #000, 80%);
// #666666
@debug mix(#FFF, #000, 40%);
5.3.5 生成颜色

生成颜色,简单来说就是指定目标颜色的参数值从而制造颜色的过程,传统的 rgbrgba 函数就是一个颜色生成函数,在 Sass 里还有其他的颜色生成函数。

@use "sass:color";

// => #8080cc
@debug color.hwb(240, 50%, 20%);
// => rgba(128, 128, 204, 0.8)
@debug color.hwb(240, 50%, 20%, 0.8);

5.4 sass:list 模块

5.4.1 list.append($list, $val, $separator: auto) 添加
  • 返回 $list 的副本,并在末尾添加 $val
  • $separatorcommaspaceslash选择一个,返回的列表分别以逗号、空格分隔或斜杠分隔。默认为 auto,返回的列表将使用与 $list 相同的分隔符。
  • 推荐使用替代内置函数 append,不需要引入 list 包。
@use "sass:list";

$arr: 1 2 3 4 5;
// => 1 2 3 4 5 -1
@debug list.append($arr, -1);
// => 1 2 3 4 5 -1
@debug append($arr, -1);
// => 1, 2, 3, 4, 5, -1
@debug append($arr, -1, comma);
// 不推荐使用斜杠,"/" 符在CSS被预留,且与除概念冲突
// => 1 / 2 / 3 / 4 / 5 / -1
@debug append($arr, -1, slash);

5.4.2 list.index($list, $value)
  • 返回 $list$value 的 索引。
  • 推荐使用替代内置函数 index,不需要引入 list 包。
@use "sass:list";

$arr: 1 2 3 4 5;
@debug list.index($arr, 2); // 2
@debug index($arr, 2); // 2
5.4.3 list.is-bracketed($list)
  • 返回 $list 是否有方括号。
  • 推荐使用替代内置函数 is-bracketed,不需要引入 list 包。
@use "sass:list";

@debug list.is-bracketed(1 2 3); // false
@debug is-bracketed(1 2 3); // false
@debug is-bracketed([1 2 3]); // true
@debug is-bracketed([1, 2, 3]); // true
5.4.4 list.join($list1, $list2, $separator: auto, $bracketed: auto)
  • 返回一个新列表,其中包含 $list1 的元素,后跟 $list2 的元素。
  • $separatorcommaspaceslash选择一个,返回的列表分别以逗号、空格分隔或斜杠分隔。默认为 auto,返回的列表将使用与 $list 相同的分隔符。
  • 如果 $bracketedauto(默认),则返回的列表将与 $list1 保持一致是否被括起来。为 true时,返回的列表将有方括号,为 false 时反之。
  • 推荐使用替代内置函数 join,不需要引入 list 包。
@use "sass:list";

$arr1: 1 2 3 4 5;
$arr2: 5 4 3 2 1;

// 1 2 3 4 5 5 4 3 2 1
@debug list.join($arr1, $arr2);
// 1 2 3 4 5 5 4 3 2 1
@debug join($arr1, $arr2);
// => 1, 2, 3, 4, 5, 5, 4, 3, 2, 1
@debug join($arr1, $arr2, comma);
// => [1, 2, 3, 4, 5, 5, 4, 3, 2, 1]
@debug join($arr1, $arr2, comma, true);

5.4.5 list.length($list) 返回长度
  • 返回 $list 的长度。
  • 推荐使用替代内置函数 length,不需要引入 list 包。
@use "sass:list";

$arr: 1 2 3 4 5;

@debug list.length($arr); // 5
@debug length($arr); // 5

5.4.6 list.separator($list)
  • 返回 $list 使用的分隔符的名称,可以是 spacecommaslash
  • 推荐使用替代内置函数 list-separator,不需要引入 list 包。
@use "sass:list";

// => space
@debug list.separator([1 2 3 4 5]);
// => space
@debug list-separator([1 2 3 4 5]);
// => comma
@debug list-separator([1, 2, 3, 4, 5]);
5.4.7 list.nth($list, $n) 取索引
  • 返回 $list 在 索引 $n 处的元素。
  • 如果 $n 为负数,则从 $list 末尾开始计数,如果索引处没有元素,则抛出错误。
  • 推荐使用替代内置函数 nth,不需要引入 list 包。
@use "sass:list";

$fruits: banana, orange, grapes, watermelon, peach, lemon, cherry;

@debug list.nth($fruits, 1); // banana
@debug nth($fruits, 1); // banana
@debug nth($fruits, -1); // cherry
@debug nth($fruits, -3); // Lemon
5.4.8 list.set-nth($list, $n, $value)
  • 返回 $list 的副本,并将索引 $n 处的元素替换为 $value
  • 如果 $n 为负数,则从 $list 末尾开始计数,如果索引处没有元素,则抛出错误。
  • 推荐使用替代内置函数 set-nth,不需要引入 list 包。
@use "sass:list";

$fruits: banana, orange, grapes, watermelon, peach, lemon, cherry;

// => pear, orange, grapes, watermelon, peach, lemon, cherry
@debug list.set-nth($fruits, 1, pear);
// => banana, pear, grapes, watermelon, peach, lemon, cherry
@debug set-nth($fruits, 2, pear);
// =>  banana, orange, grapes, watermelon, peach, lemon, pear
@debug set-nth($fruits, -1, pear);

5.4.9 list.zip($lists…)
  • 将 $lists 中的每个列表合并为一个二维列表,这与 Python 的 zip 函数类似,通过此函数,可以在循环时候同时遍历两个列表。
  • 若多个列表的长度不一致,则依据木桶短板原理,取最短的那个列表的长度进行合成。
  • 推荐使用替代内置函数 zip,不需要引入 list 包。
@use "sass:list";

$arr1: 1 2 3 4 5;
$arr2: 5 4 3 2 1;
$arr3: a b c d e;

// => [[1 5], [2 4], [3 3], [4 2], [5 1]]
@debug list.zip($arr1, $arr2);
// => [[1 5], [2 4], [3 3], [4 2], [5 1]]
@debug zip($arr1, $arr2);
// => [[1 5 a], [2 4 b], [3 3 c], [4 2 d], [5 1 e]]
@debug zip($arr1, $arr2, $arr3);

// 同时循环两列表
@each $p1, $p2 in zip($arr1, $arr2) {
  @debug "p1: #{$p1}, p2: #{$p2}"
}

5.5 sass:map 模块

5.5.1 map.merge($map1, $map2) 合并
  • 合并两个映射类型。如果你用过其他语言的合并,比如在 JavaScript 里的 Object.assign 方法,那么你一定能很快理解。
  • 如果在合并时遇到相同的键,则后面参数的 $map2 将进行覆盖操作。
  • 推荐使用替代内置函数 map-merge,不需要引入 map 包。
@use "sass:map";

$map1: (1: a, 2: b, 3: c);
$map2: (3: d, 4: f, 5: h);

// => (1: a, 2: b, 3: d, 4: f, 5: h)
@debug map.merge($map1, $map2);
// => (1: a, 2: b, 3: d, 4: f, 5: h)
@debug map-merge($map1, $map2);
// => (3: c, 4: f, 5: h, 1: a, 2: b)
@debug map-merge($map2, $map1);

5.5.2 map.deep-merge($map1, $map2) 深合并
  • map.merge 相同,但嵌套映射值也会递归合并。
@use "sass:map";

$d-map1: (a: 1, b: 2, c: (aa: 1, bb: 2));
$d-map2: (d: 2, c: (cc: 3, dd: 4));

// 普通合并会进行直接覆盖操作
// => (a: 1, b: 2, c: (cc: 3, dd: 4), d: 2)
@debug map-merge($d-map1, $d-map2);

// 深合并会递归对是map类型的元素也进行合并
// => (a: 1, b: 2, c: (aa: 1, bb: 2, cc: 3, dd: 4), d: 2)
@debug map.deep-merge($d-map1, $d-map2);
5.5.3 map.get($map, $keys…) 取值
  • $map 后允许传入多个要搜寻的键,将一一对嵌套的映射类型进行取值,这有点类似于 JS 里的 obj.foo.attr
  • 如果 $keys 为一个参数,则取与 $map 表层对应的字段,类似于 JS 里的 obj.foo
  • 如果映射没有与键关联的值,则返回 null
  • 推荐使用替代内置函数 map-get,不需要引入 map 包。
@use "sass:map";

$map-data: (a: 1, b: 2, c: (aa: 1, bb: 2), d: 4);

// => 2
@debug map.get($map-data, b);
@debug map-get($map-data, b);

// => null
@debug map-get($map-data, e);
// 深取值 => 1
@debug map-get($map-data, c, aa);
// 深取值 => 2
@debug map-get($map-data, c, bb);
// 深取值 => null
@debug map-get($map-data, c, cc);

5.5.4 map.keys($map)
  • 返回 $map 中所有键的逗号分隔列表。
  • 推荐使用替代内置函数 map-keys,不需要引入 map 包。
@use "sass:map";

$map-data: (a: 1, b: 2, c: (aa: 1, bb: 2), d: 4);

// =>  a, b, c, d
@debug map.keys($map-data);
// =>  a, b, c, d
@debug map-keys($map-data);
5.5.5 map.remove($map, $keys…)
  • 删除 $map 里与 $keys 包含的键,并且返回删除之后的副本。
  • 注意这里的 $keysmap.get 方法里的 $keys不一样,指的不是嵌套的键。
  • 推荐使用替代内置函数 map-remove,不需要引入 map 包。
@use "sass:map";

$map-data: (a: 1, b: 2, c: (aa: 1, bb: 2), d: 4);

// => (a: 1, b: 2, c: (aa: 1, bb: 2))
@debug map.remove($map-data, d);
// => (a: 1, b: 2, c: (aa: 1, bb: 2))
@debug map-remove($map-data, d);
// => (a: 1, b: 2)
@debug map-remove($map-data, c, d);

5.5.6 map.set($map, $keys…, $value)
  • 如果 $keys 为一个参数,这直接将指定的键设置成对应的 $value 并返回更新之后的副本。
  • 如果 $keys 为多个参数,则按照 $keys 递归查找要更新的嵌套映射,在此过程中如果有一个键的值不是映射,则将该键处的值设置为空映射再继续下一步,直至将 $keys 的最后一个键设置成对应的 $value,最后再返回更新之后的副本。
@use "sass:map";

$map-data: (a: 1, b: 2, c: (aa: 1, bb: 2), d: 4);

// => (a: 1, b: 2, c: (aa: 1, bb: 2), d: 4, e: 5)
@debug map.set($map-data, e, 5);
// => (a: 1, b: 2, c: (aa: 1, bb: 2, cc: 3), d: 4)
@debug map.set($map-data, c, cc, 3);
// => (a: 1, b: 2, c: (aa: 1, bb: 2), d: 4, f: (aa: (aaa: aaaa 1)))
@debug map.set($map-data, f, aa, aaa, aaaa 1);
5.5.7 map.values($map)
  • 返回 $map 中所有值的逗号分隔列表。
  • 推荐使用替代内置函数 map-values,不需要引入 map 包。
@use "sass:map";

$map-data: (a: 1, b: 2, c: (aa: 1, bb: 2), d: 4);

// => 1, 2, (aa: 1, bb: 2), 4
@debug map.values($map-data);
// => 1, 2, (aa: 1, bb: 2), 4
@debug map-values($map-data);
5.5.8 map.has-key($map, $keys…)
  • $keys 为一个参数,则直接判断 $map 里是否存在这个键。
  • $keys 为多个参数,将递归查找嵌套映射里是否存在指定的键,若在此过程中遇到不是映射类型的键,则直接返回 false
  • 推荐使用替代内置函数 map-has-key,不需要引入 map 包。
@use "sass:map";

$map-data: (a: 1, b: 2, c: (aa: 1, bb: 2), d: 4);

@debug map.has-key($map-data, f); // false
@debug map-has-key($map-data, b); // true
@debug map-has-key($map-data, b, bb); // false
@debug map-has-key($map-data, c, bb); // true

5.6 sass:meta 模块

5.6.1 meta.function-exists($name, $module: null) 是否存在该函数
  • 根据 $name 判断函数是否被定义为内置函数或用户定义函数。有点类似于反射,但又不完全相同。
  • $module 用于检查使用 @use [模块名] 语法的命名空间下是否匹配存在 $name,默认为 null 既当前命名空间。
  • 推荐使用替代内置函数 function-exists,不需要引入 meta 包。
@use "sass:meta";

@function func() {}

@debug meta.function-exists(foo); // false
@debug function-exists(foo); // false
@debug function-exists(to-lower-case); // true
@debug function-exists(func); // true
5.6.2 meta.global-variable-exists($name, $module: null)
  • 根据$name(不带 $)判断全局变量是否存在。
  • $module 用于检查使用 @use [模块名] 语法的命名空间下是否匹配存在 $name,默认为 null 既当前命名空间。
  • 推荐使用替代内置函数 global-variable-exists,不需要引入 meta 包。
@use "sass:meta";

$color1: #FFF;
$color2: teal;

@debug meta.global-variable-exists("color1"); // true
@debug global-variable-exists("color1"); // true
@debug global-variable-exists("color2"); // true
@debug global-variable-exists("color3"); // false
5.6.3 meta.mixin-exists($name, $module: null)
  • 根据 $name 判断混合是否存在。
  • $module 用于检查使用 @use [模块名] 语法的命名空间下是否匹配存在 $name,默认为 null 既当前命名空间。
  • 推荐使用替代内置函数 mixin-exists,不需要引入 meta 包。
@use "sass:meta";

@mixin container() {}

@debug meta.mixin-exists(container); // true
@debug mixin-exists(container); // true
@debug mixin-exists(abc); // false
@debug mixin-exists(a); // false
5.6.4 meta.type-of($value) 数据类型获取
  • 返回 $value 的类型。
  • 推荐使用替代内置函数 type-of,不需要引入 meta 包。
@use "sass:meta";

@debug meta.type-of("123"); // string
@debug type-of("123"); // string
@debug type-of(123); // number
@debug type-of([1, 2, 3, 4]); // list
@debug type-of((a: 1, b: 2, c: 3)); // list
@debug type-of(#FFF); // color
@debug type-of(true); // bool
@debug type-of(null); // null
5.6.5 meta.variable-exists($name)
  • 根据$name(不带 $)判断当前作用域下的变量是否存在,这与判断全局变量的 meta.global-variable-exists 不同。
  • 推荐使用替代内置函数 variable-exists,不需要引入 meta 包。
@use "sass:meta";

$global_var: 123;

@function foo() {
  $var: 123;
  @debug variable-exists("var"); // true
  @debug global-variable-exists("global_var"); // true
  @return null;
}


@debug foo();
@debug variable-exists("global_var"); // true
@debug global-variable-exists("global_var"); // true
@debug variable-exists("var"); // false

六、自定义模块包

除了使用官方自带的扩展模块包,在 Sass 里我们可以编写自己的自定义模块。
作为自定义模块而不是被自行编译的 Sass 文件要以 _ 作为开头命名,主要目的是为了告诉 Sass 在编译的时候,不要去编译这些文件,而在使用 @use 进行导入的时候可以忽略_

// ./_xz-helper.scss

// 颜色变量 👇

/* 主题颜色 */
$primary-color: #675AFF;
$dark-primary-color: #835CFF;
$shallow-primary-color: #84C1FF;

/* 页面背景颜色 */
$page-bg-color: #F7F8FA;

/// 制造一定存在的值,并确保附加单位
/// @param {number} $val 原始值
/// @param {number} $def [0] 若值为空时的替换值,默认为0
/// @param {string} $unit [px] 目标单位,默认为px
/// @return {number} 处理值
/// @group xz-helper
/// @author XianZheCwx
@function makeExistVal($val, $def: 0, $unit: px) {
  $last: $def;
  @if $val {
    $last: $val;
  }
  // 单位是否存在
  @if unitless($last) {
    $last: $last + $unit;
  }

  @return $last;
}

/// 页面安全容器
/// @param {number} $minHeight [98.2vh] 最小高度,默认98.2vh
/// @param {string} $bgc [transparent] 背景颜色,默认transparent
/// @param {number} $around [30px] 边距,默认30px
/// @param {number} $top [0] 顶部额外边距,默认0
/// @param {number} $bottom [36px] 底部额外边距,默认36px。
///         若为false,但启用安全边距和边距的情况下,使用边距作为下边距。
/// @param {boolean} $padding [true] 是否启用边距,默认true
/// @param {Boolean} $safeBottom [true] 是否启用底部安全边距
/// @group xz-helper
/// @author XianZheCwx
@mixin container(
  $minHeight: 97vh, $bgc: transparent,
  $around: 30px, $top: 0, $bottom: 36px,
  $padding: true, $safeBottom: true
) {
  box-sizing: border-box;

  @if $minHeight {
    min-height: $minHeight;
  }

  @if $bgc {
    background-color: $bgc;
  }

  @if $padding {
    padding: calc(
      #{makeExistVal($around)}
      + #{makeExistVal($top)}) $around makeExistVal($bottom);
  }

  @if $safeBottom {
    @if $bottom {
      padding-bottom: calc(#{$bottom} + env(safe-area-inset-bottom, 0));
    } @else if ($padding) {
      padding-bottom: calc(#{makeExistVal($around)} + env(safe-area-inset-bottom, 0));
    } @else {
      padding-bottom: env(safe-area-inset-bottom, 0);
    }
  }
}

// 将./_xz-helper.scss作为模块使用
@use "xz-helper";

@debug xz-helper.$primary-color;

.demo {
  @include xz-helper.container();
}


七、SASS 命令行指令

对于不同的 Sass 依赖环境,命令选项基本相同,若使用过程中发现有部分差异可以自行查看 libsass 文档。

6.1 一对一模式

  • 将单个输出文件编译到单个输出位置。根据输入文件不同的扩展名来分别解析,.scss扩展名以 Scss 语法解析,.sass扩展名以缩进语法解析,.css扩展名原封不动复制,若不带有扩展名,则默认以 Scss 进行解析。
  • 若不指定输出文件,则将编译内容输出到终端上。
# sass <input.scss> [output.css]

# 输出到文件
sass ./test.scss ./test.css
# 输出到控制台
sass ./test.scss 

6.2 多对多模式

  • 与一对一的不同就是,多对多可以同时编译多个文件到多个输出位置,输入与输出用冒号分割,还可以将一个目录中的所有 Sass 文件编译到另外一个目录里。
  • 使用多对多模式编译单个文件也是可以的,并且编译一整个目录时,以 _ 开头的文件将被忽略。
  • 对于目录编译,若内部存在额外文件夹,则将这些文件夹依次递归编译。输出路径最好与输入路径进行隔离,以免多次编译的时候将上一次的输出新增到下次的输出中。
# sass [<input.scss>:<output.css>] [<input/>:<output/>]...

# 单个文件编译
sass ./test.scss:./test.css
# 多个文件编译
sass ./test.scss:./test.css ./test2.scss:./test2.css
# 目录下所有文件编译
sass ./scss:./dict/css
# 目录下所有文件编译(不推荐:输出路径位于输入路径内不利于重复编译)
sass ./scss:./scss/dict

6.3 命令选项

6.3.1 --stdin 标准输入

此选项并不重要,一般用于调试用。
想要了解什么是标准输入,可以查看这里 标准输入,简单来讲就是通过控制台编写的 Sass 代码直接通过 Sass 工具进行转义,而 --stdin 则是告诉 Sass 从标准输入上获取代码。

# 控制台输出
echo .title {font-size: 40px;} | sass --stdin
# .title {
#   font-size: 40px;
# }

# 输出到文件中
echo .title {font-size: 40px;} | sass temp.css --stdin
6.3.2 --indented 缩进语法
  • –stdin 选项用于便于 Scss 语法,而在标准输入下想要编译老版的 Sass 缩进语法,那只需要再加一个 --indented 选项。
  • 需要注意的是,在 Windows 平台下终端 echo 无法对特殊字符进行转义解析,而在 Liunx 之类的平台下可以通过 -e 选项转义,如果在 Windows 平台使用特殊字符时出现异常,这并不是 Sass 的问题。
  • 在 Liunx 之类的平台下时,只需要单独一个 --indented 选项即可,再配合解析特殊字符选项 -e
# Liunx 下命令
$ echo -e '.demo\n  font-size: 66px' | sass --indented

# Windows 下命令
echo /* 123 */ | sass --indented --stdin
6.3.3 --style 压缩控制

此选项用于控制编译生成的文件要经过什么样的压缩级别👇:

  • expanded(默认):将每个选择器和声明写在自己的行上。
  • compressed:删除尽可能多的额外字符,并将整个样式表写在一行上,在此选项上注释会被删除。
# 压缩模式
sass input.scss output.css --style=compressed
6.3.4 --charset CSS编码控制
  • 此选项控制的是生成 CSS 中的 @charset "UTF-8";,即指定样式表中要使用的字符编码,默认情况下启用,不管加不加这个选项都是一样的。
  • --charset 更改为 --no-charset 可以反向更改,在任何情况下强制不生成 @charset "UTF-8";
  • @charset "UTF-8"; 的生成是自动的,并不是强制生成,只有检测到任何除了 ASCII 字符的出现时,才会添加到编译成的 CSS 文件里。非 ASCII 字符包括的范围是所有,包括使用了中文注释,或则 Unicode 编码的表情包时,都会出现。
.demo {
  font-size: 88px;

  /* Unicode表情以及这条注释本身也会带来@charset "UTF-8"; */
  &::after {
    content: "🥙";
  }
}
@charset "UTF-8";
.demo {
  font-size: 88px;
  /* Unicode表情以及这条注释本身也会带来@charset "UTF-8"; */
}
.demo::after {
  content: "🥙";
}
6.3.5 --update 仅更新
  • 简单来说,--update 选项的作用是在编译的时候增加一个判断修改时间的机制,若需要编译 SCSS 文件的修改日期比已经生成存在的 CSS 文件的修改日期还晚,那就进行编译,并且在控制台输出一行提示告诉你有进行编译,反正不进行任何操作。
  • 此选项命名与 --watch 类似,但作用却截然不同,不要将两者混淆在一起。
sass input.scss output.css --update
>>> Compiled input.scss to output.css.
6.3.6 --no-source-map 不生成源映射
sass input.scss output.css --no-source-map
6.3.7 --embed-source-map 嵌入源映射

如果既需要源映射,又不想又生成一个文件的话,可以考虑使用此选项。但此方法的弊端也很明显,生成的 CSS 文件存在体积冗余,这对于客户端来说是不友好的。

sass input.scss output.css --embed-source-map
6.3.8 --watch 文件监听 🏆
  • 可以说是最实用的选项之一,在文件发生更改的时候,自动编译成 CSS 文件,避免了重复输入命令的麻烦,可以算的上最简单的自动化处理。
  • --watch 配合使用的还有一个 --poll 选项。默认情况下 Sass 对于文件更改的监听原理是通过文件系统在文件发生更改的时候通知,这有点类似于异步 IO,而如果使用 --poll,则是通过 Sass 自己轮询判断文件是否发生更改,这又与非阻塞 IO 类似。一般情况下在无法使用原生文件系统监听功能的环境,比如网络文件的情况下使用。
sass input.scss output.css --watch
>>> [2024-01-29 18:03] Compiled input.scss to output.css.
>>> [2024-01-29 18:03] Compiled input.scss to output.css.

# Sass 轮询监听
sass input.scss output.css --watch --poll
>>> [2024-01-29 18:08] Compiled input.scss to output.css.
>>> [2024-01-29 18:08] Compiled input.scss to output.css.
6.3.9 其他选项
  • –quiet: 安静编译,将忽略所有警告
  • –version: 版本号
  • –help: 打印 Sass 帮助文档

八、自动化编程

Sass 除了在命令行使用之外,本身也拥有能够直接通过JavaScript处理的API,类似于 Vue、React 前端框架,本质上就是在使用 Sass 在 Javascript 上的 API。
此章节作为本篇文章的结尾,将会用到上面讲到的大部分的知识点,起到承上启下的作用,现如今大部分 Web 框架已经内置对样式预处理的支持,需要自己去手动流程化处理的情况比较少,感兴趣的小伙伴可以研究研究哦。
使用前端流程工具 gulp 编写一套自动化脚本,针对于 Sass 进行一系列任务流程,比如浏览器兼容前缀、样式压缩,包括先前提到过的 SassDoc 工具等,将 Sass 编译成符合要求的 CSS 并最终生成一份 Sass 代码文档手册。
由于代码量过多,自动化编程这部分的代码内容将放到开源仓库上,另外这是 SassDoc 对于附注的规则:http://sassdoc.com/annotations/ ,对其他使用插件感到疑惑的小伙伴不妨去查阅一下对应插件的文档哦。

温馨提示,对于 SassDoc 的使用必须严格遵循其注释规则,否则将在编译时抛出异常,SassDoc 也有一些不完善的地方,对于某块错误的规则代码并不会提示具体在那个地方,还请养成良好的编写习惯。’

// 部分核心代码 👇
......
export function sassCompileTask(next: TaskFunctionCallback) {
  Print.info("Sass编译开始...");
  const compile = new SassCompile(SASS_DIR);
  compile.exec(OUT_CSS_DIR);
  Print.info("Sass编译结束...");
  next();
}

export function sassInjectTask(next: TaskFunctionCallback) {
  (new HtmlCompile(HTML_DIR)).linkCss(OUT_CSS_DIR, OUT_DIR);
  Print.success("Sass已注入到Html...");
  next();
}

export function sassDocsTask(next: TaskFunctionCallback) {
  Print.info("Sass文档生成开始...");
  try {
    (new SassDosc(SASS_DIR)).generate(OUT_DOCS_DIR);
  } catch (e) {
    Print.error("Docs文档生成错误");
    Print.error(e, { inverse: true });
  }
  Print.info("Sass编译结束...");
  next();
}

export const sassTask = parallel(
  series(sassCompileTask, sassInjectTask),
  series(sassDocsTask)
);

图8-1 自动化编程成品目录

图8-2 Sass文档预览

对于 gulp 来讲,本身关于 Sass 的插件可不少,当然也可以通过其他更加保姆级的插件进行编写。


代码托管🌐

八、自动化流程 章节中的项目代码👇:

参考资料💘

🍅因发布平台差异导致阅读体验不同,源文贴出:《Sass 核心程序设计指南》


推荐博文🍗

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值