• AaronConlon.dev
  • Talk
  • Weekly
  • 工具
  • 编程
  • 翻译
  • 资讯
  • 通识

Sass 浅解

2022年10月05上次更新于 大约 2 年前
编程

前言

时至今日,Sass已经可以视为一门编程语言了。笔者目前对 sass 编写样式表一知半解,在写手头的项目时十分不顺,效率低下,因此打算花两个小时重学 sass。

某种意义上而言本文仅仅是笔者对于文档的学习和思索,更好的学习方法依然是阅读文档,但是于我于有兴趣阅读此文的朋友而言都一样,我都希望我们能够有所得。

大纲

  • 语法

  • 样式规则

  • 变量

  • 操作

  • 模块

  • 断点和混入

语法

基础

sass 支持两种语法,二者可以互相载入,分别是:

  • scss

  • sass

二者的区别从扩展名开始,scss对css的兼容性是最好的,几乎所有的css都是有效的scss写法(除了错误的语法被忽略的那部分),正因如此,此语法广受推崇。

当你 copy 一段让人拍案叫绝的代码到自己的样式表中时,你什么都不需要做

笔者喜欢.sass第二种语法,本文内容建议使用.sass来理解。笔者很懒,.sass可以少写些许花括号和分号,唯一需要注意的是.sass对缩进的写法非常严格,不过这不是什么大问题。

解析样式表

A Sass stylesheet is parsed from a sequence of Unicode code points. It's parsed directly, without first being converted to a token stream.

sass需要通过编译器去编译成css,流行的编译器如下:

  • Dart Sass

  • LibSass

  • Ruby Sass

通常我们写样式都用不上除了英文和数字外的字符,上述编译器Dart Sass只支持UTF-8字符,只有当我们需要用超过UTF-8字符集的字符来写样式时,才需要考虑不使用Dart Sass。

当编译器遇到无效的语法时,解析会失败并且提供错误信息以便开发者修正错误语法。

样式表结构

除了和css类似的样式属性声明外,sass还支持许多增强型特性。

sass由一系列的语句块组成,语句块之间可以嵌套其他语句块。

语句

我们可以将语句分为四种:

  • css 语句

    • 样式规则

    • css @规则,例如 @media

    • Mixin

    • @at-root

  • 普通语句

    • 变量声明 $var: value

    • 流程控制 @if

    • 规则控制 @error、@debug等

  • 顶层语句(样式表顶层或嵌套的 css 顶层)

    • 文件引入 @import

    • 混入 @minxin

    • 函数定义 @function

  • 其他语句

    • @extend语句写在样式属性中

前者易于理解,跟css更为接近。后者形如则是sass的核心,有了这些才撑起了整体结构。

即使对上述词汇了解不多也不需要担心,接下来逐一拆解学习。

表达式

表达式语法即SassScript。

表达式位于属性或变量声明的右侧,表达式产生一个值。显然,任意有效的css值都可以理解为sass的表达式。

在sass中,表达式用于传给mixin或function,亦或配合@if控制流,甚至进行算术运算。

最简单的表达式只表示静态的值,例如:

  • Numbers: 100px

  • String: "bolder"

  • Colors: #eee

  • Booleans: true or false

  • Lists: 1px 2px 0 1px

  • Maps: 键值对映射,例如("color": red, "font-size": 12px)

其他表达式:

  • 变量$var

  • 函数调用var(--color-bg-1)

  • 特殊函数calc(1px + 100%)或url(...)

  • 父选择器 &

  • !important

操作符

sass支持许多操作符语法:

  • == or !=比较值

  • + - * / % 数学计算

  • < <= > >=逻辑判断

  • and or not布尔值判断

  • + - /字符串拼接

  • ()优先级控制

注释

sass是编程语言,有注释很正常。

在 scss 中写注释,可以这样写:


// 这个注释不会出现在CSS中



/* 这个将会出现在CSS中,压缩模式下不会有 */



/* CSS中显示插值的计算结果

 * 1 + 1 = #{1 + 1} */



/*! 在压缩模式下也会显示 */



p /* 多行注释可以写在任何允许 */

  /* 使用空格的地方。 */ .sans {

  font: Helvetica,

    // 单行注释也是

    sans-serif;

}

回到正题,在sass中更简洁:


//  这个注释不会出现在CSS中

    这个也被注释掉了



/*  这个将会出现在CSS中,压缩模式下不会有



/*  CSS中显示插值的计算结果

 *  1 + 1 = #{1 + 1}



/*! 在压缩模式下也会显示



p .sans

  font: Helvetica, /* 内联注释必须是闭合的 */ sans-serif

当你打算编写样式库时,可以在文档的最上方使用三斜杠///来开头编写文档,最终SassDoc等工具可以读取并且生成美观的文档。

特殊函数

此前在表达式处提及特殊函数,在此重申之。

url()是css中常用的函数,在sass中需要用特殊的逻辑来解析它。简单来看,可以按css规则来调用,也可以使用sass变量或函数调用最后的值来解析。举个例子:


$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-family: "Roboto"

    font-weight: 100





@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-family: "Roboto"

    font-weight: 300

此外,还有其他的特殊函数:

css 中的**calc()**跟sass算法冲突,总之一句话,除了sass插值用于动态注入值之外,其他内容都作为原生css内容解析。

举个例子:


.logo

  $width: 800px

  width: $width

  position: absolute

  left: calc(50% - #{$width / 2})

  top: 0

Sass 中的min()和 max()二者在css支持之前就实现了,当这类特殊函数中不包含除了插值之外的任何SassScript的特性时,解析为css原生函数。

举个例子:


$padding: 12px



.post

  // Since these max() calls don't use any Sass features other than

  // interpolation, they're compiled to CSS max() calls.

  // 插值和原生的 env 函数

  padding-left: max(#{$padding}, env(safe-area-inset-left))

  padding-right: max(#{$padding}, env(safe-area-inset-right))





.sidebar

  // Since these refer to a Sass variable without interpolation, they call

  // Sass's built-in max() function.

  // 变量属于 SassScript

  padding-left: max($padding, 20px)

  padding-right: max($padding, 20px)

除此之外,还有众多内建函数,我们在配合相关知识点时候再总结。

变量

$<variable>:<expression> 即变量定义,使用$开头进行定义,表达式求值。

Sass 标识符和变量名都将连字符-和下划线_视为相同的字符。

任何地方都可以申明变量,sass的变量会被编译,因此一次只能有一个值,可以重新定义同名的变量,使用新值覆盖旧值,再使用新的变量和值,整个过程是命令式的。

在开发sass库时可以使用!default来编写变量默认值,普通开发者则无需关心。

举例:


// _library.sass

$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.sass

$black: #222

$border-radius: 0.1rem



@import 'library'

显然,我们在使用他人开发的库时,可以预先定义一些变量来个性化样式表。库的开发者也可以用默认样式兜底。

既然有变量,当然也有变量作用域。

一个sass文件顶层声明的变量是整个文件全局可访问的,并且当这个文件被其他文件引入时,顶层的变量将被共享。

在块中声明的变量却是本地可用的,在当前块内可用,当块内变量和全局变量同名时,在块内的优先级更高。

但是依然有办法灵活应用规则,举个例子:


$variable: first global value;



.content {

  $variable: second global value !global;

  value: $variable;

}



.sidebar {

  value: $variable;

}

使用!global标志可以显式指定此变量为全局变量。

另外,在流程控制中声明的变量具有特殊的作用域,并且流程控制内只能给外部存在的变量赋值,而不能声明新的变量。

举个scss的例子:


$dark-theme: true !default;

$primary-color: #f8bbd0 !default;

$accent-color: #6a1b9a !default;



@if $dark-theme {

  $primary-color: darken($primary-color, 60%);

  $accent-color: lighten($accent-color, 60%);

}



.button {

  background-color: $primary-color;

  border: 1px solid $accent-color;

  border-radius: 3px;

}

引入此库的其他文件可以自定义暗色主题来控制当前样式的结果。

还有一个问题,如何判断变量是否存在?sass提供了以下函数:

  • Variable-exists()

  • Global-variable-exists()

二者分别判断当前作用域内变量和全局变量。

插值

只要我们需要将表达式的结果嵌入到css块中去,就可以使用#{}包装的表达式,这种语法被称为插值。

我们可以在任何地方使用插值,插值就像表达式替换。sass以一种直白的方式来让插值生效,举个例子:


@mixin corner-icon($name, $top-or-bottom, $left-or-right) .icon-#{$name} background-image:

  url("/icons/#{$name}.svg") position: absolute #{$top-or-bottom}: 0 #{$left-or-right}:

  0 @include corner-icon("mail", top, right);

先不论mixin和include是什么,无论你传入什么值过去,编译后将直接在原位置进行替换:


.icon-mail {

  background-image: url("/icons/mail.svg");

  position: absolute;

  top: 0;

  left: 0;

}

需要注意的是,当插值花括号中包含引号时,带引号的字符串周围的引号会被删除。

样式规则

进入正题。

css也已经存在嵌套的草案,很棒。

sass支持嵌套,这是sass随处可见的知识点,但凡看过 sass 的开发者都知道,无需赘述。关键在于:控制嵌套深度 ,嵌套很棒但过深的嵌套生成的css代码就越多,三层足以避免过犹不及。

选择器列表支持和组合选择器可以直接使用css语法。

举个例子 🌰:


.enlarge

  font-size: 14px

	margin: 1rem

		top: 2rem

  transition:

    property: font-size

    duration: 4s

    delay: 2s



  &:hover

    font-size: 36px

	& > h1

		color: #333

	&r

		color: purple



	.other

		color: red

上述enlarge类内嵌套了other类,并且使用了&父节点选择器实现了.enlarge:hover、.enlarge > h1甚至是.enlarger这样的选择器效果。

其中margin属性名还可以视为命名空间嵌套着使用,编译出margin-top: 2rem的样式。

此外,编译器都能对多个父选择器做出判断:


ul, ol

  text-align: left



  & &

    padding:

      bottom: 0

      left: 0

上述父选择器按顺序进行编译,不必担心ul和ol在使用父选择器时出现顺序异常。

&父选择器可以作为判断条件,例如:

if(&, '&.app-background', '.app-background')

当前处于子级范围时,采用前者,否则采用后者。

即使是 css,选择器就已经非常强大了。

此外,还可以根据特定的条件去设置属性和值,此时声明一个null作为表达式,最终将不会编译此属性。

举个例子:


$rounded-corners: false



.button

  border: 1px solid black

  border-radius: if($rounded-corners, 5px, null)

最终的css文件将不包含border-radius属性。

css支持自定义属性,也称为css变量。这个机制允许开发者灵活的设置属性,同时JavaScript能够访问这些值。

在sass中对css自定义属性的处理需要关注的核心只有一个:仅有插值是动态的,其他标记都将原样编译为css,举个例子:


$primary: #81899b

$accent: #302e24

$warn: #dfa612

// 插值将顺利转换

:root

  --primary: #{$primary}

  --accent: #{$accent}

  --warn: #{$warn}



  // Even though this looks like a Sass variable, it's valid CSS so it's not

  // evaluated.

  // 下一行将原样编译为 css

  --consumed-by-js: $primary

此外,还需要了解一个非常强大的sass选择器:占位符选择器。

占位符选择器和其他css选择器类似,它以%开头,并且不会包含在css输出中,举个例子:


.alert:hover, %strong-alert

  font-weight: bold





%strong-alert:hover

  color: red

最终编译的结果如下:


.alert:hover {

  font-weight: bold;

}

但是,我们却可以在sass中引用其内容对其他选择器进行扩展,减少代码量,举个例子:


%toolbelt

  box-sizing: border-box

  border-top: 1px rgba(#000, .12) solid

  padding: 16px 0

  width: 100%



  &:hover

    border: 2px rgba(#000, .5) solid



.action-buttons

  @extend %toolbelt

  color: #4285f4



.reset-buttons

  @extend %toolbelt

  color: #cddc39

如上所示,.action-buttons和.reset-button都被扩展了,最后的编译结果为:


.action-buttons,

.reset-buttons {

  box-sizing: border-box;

  border-top: 1px rgba(0, 0, 0, 0.12) solid;

  padding: 16px 0;

  width: 100%;

}

.action-buttons:hover,

.reset-buttons:hover {

  border: 2px rgba(0, 0, 0, 0.5) solid;

}



.action-buttons {

  color: #4285f4;

}



.reset-buttons {

  color: #cddc39;

}

显然我们可以灵活编写占位符选择器,并且选择适当的情况下扩展其他选择器,还不用关心占位符选择器这部分代码,因为最终在不引用的情况下会忽略这部分而编译整体。

模块化

编程语言模块化可以将复杂的单文件拆分成多个小文件,将复杂环境拆分梳理为若干小环境,降低开发者的心智压力。

按照惯例,开发者将以下划线开头的sass文件视为片段文件,这些片段文件将被@import指令使用,如果不然sass编译器将忽略编译这些片段文件。

一句话,使用下划线开头的文件作为片段文件,在需要用到片段文件的sass文件使用@import指令将之引入,引入时需要忽略下划线。

css 具有自己的 @import 规则,浏览器在解析到 css 的导入行为时将会发起一个 http 请求获取此目标文件。

sass将会获取目标导入文件,并且将此文件和被导入的文件结合,编译出最终样式表。

当将片段文件导入到目标文件后,目标文件可以使用片段文件中的内容。

规则

Sass 的大部分额外功能都是在 CSS 之上添加新@规则

以下是我们可以在日常开发中使用起来的规则:

  • @import 引入样式、mixin、函数、变量

  • @mixin、@include 增强复用样式块

  • @function 增强内置函数外的函数功能

  • @at-root 将样式放入 CSS 文档的根目录中

  • @error、@warn、@debug 用于调试

  • @if、@each、@for、@while 控制函数逻辑

接着,我们逐一加深这些知识点的印象。

@import

原生的css让浏览器呈现时增加http请求,而sass则在编译期间解决问题。

导入多个文件时可以添加逗号作为分隔,使用.sass语法时不需要为URL添加括号。并且导入的文件内容将会放在@import指令出现的位置,但嵌套导入的 mixin、函数、变量依然具有原作用域。

另外,对于导入的文件URL,编译器都能很好的自动进行忽略扩展名的文件查找,并且在路径上统一使用/作为分隔符。开发者不需要为不同平台的地址问题费心思了。

对于加载路径的优先级而言,本地文件的优先级高于node_modules下的库文件。

如果导入的是一个css文件(本地或远程)亦或使用了url()或媒体查询时,最终将会直接作为原生的css导入使用。

@mixin

通过@mixin定义一个具名的mixin,通过@include mixinName 使用mixin。

从笔者个人层面来看,mixin就是sass这门语言的"函数"

mixin能够让我们轻松通过复用机制减少非语义类的使用,并且mixin非常灵活。

接下来我直接看几个mixin及其用法:


@mixin square($size, $radius: 0) width: $size height: $size @if $radius != 0 border-radius:

  $radius .avatar @include square(100px, $radius: 4px);

如上所示,可以预定义一个mixin,参数支持具有默认值的可选参数,这一点跟JS很像。

再看一个示例:


@mixin square($size, $radius: 0, $n: 1) width: $size height: $size @if $n != 1 border:

  #{$n}px .avatar @include square(100px, $n: 4);

可以传递具名的参数,这样可以忽略传参的顺序。

再看:


@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");

如果最后一个参数以...结尾,即可将传入的额外参数视为一个可以迭代的列表。

nth 是内建函数

最终的编译结果为:


input.name {

  position: absolute;

  height: 150px;

  margin-top: 0px;

}



input.address {

  position: absolute;

  height: 150px;

  margin-top: 150px;

}



input.zip {

  position: absolute;

  height: 150px;

  margin-top: 300px;

}

再看一个.scss示例:


@mixin syntax-colors($args...) {

  @debug keywords($args); // (string: #080, comment: #800, $variable: $60b)



  @each $name, $color in keywords($args) {

    pre span.stx-#{$name} {

      color: $color;

    }

  }

}



@include syntax-colors($string: #080, $comment: #800, $variable: #60b);

传递多个参数,mixin的定义参数位不定键名参数,最终可以使用内建的函数获取到键值对。

再看:


@mixin btn($args...) {

  @warn "The btn() mixin is deprecated. Include button() instead.";

  @include button($args...);

}

看到这里,是否能感受到我们可以使用sass很方便的创建某个UI库的样式表,可以为团队创建基础的sass片段。

另外,mixin还支持内容块,只需要在mixin中添加一行@content即可,举个例子:


@mixin hover &: not([disabled]): hover @content .button border: 1px solid black

  @include hover border-width: 2px;

到这里我已经觉得复杂了,还可以支持多个@content和给内容块传参,善用sass真不容易,需要大量的使用场景和解决问题的经验。

@function

通过@function来定义函数,并且可以在任何地方直接调动函数,就像我们使用css原生的函数一样。

此前笔者提及mixin如sass的函数,然而实际上sass的函数由@function定义,二者是单独的概念。

来看一个基础函数pow:


@function pow($base, $exponent) $result: 1 @for $_ from 1 through $exponent $result:

  $result * $base @return $result .sidebar float: left margin-left: pow(4, 3) * 1px;

如其名,计算乘方。关注函数的标识、参数、结构控制、返回值,这部分跟mixin是一样的。

此外,sass内建了诸多强大的函数方便我们开发使用,针对不同类型有:

  • 颜色

  • 列表

  • 映射表

  • 数学模块

  • meta

  • 选择器

  • 字符串

sass有诸多语法和数据类型,根据这些数据可以配合这部分内建函数处理问题,例如我们需要调整一个颜色值:


color.adjust($color,

  $red: null, $green: null, $blue: null,

  $hue: null, $saturation: null, $lightness: null,

  $whiteness: null, $blackness: null,

  $alpha: null)

adjust-color(...) //=> color



@debug color.adjust(#6b717f, $red: 15); // #7a717f

@debug color.adjust(#d2e1dd, $red: -10, $blue: 10); // #c8e1e7

@debug color.adjust(#998099, $lightness: -30%, $alpha: -0.4); // rgba(71, 57, 71, 0.6)

这个内建函数实在是强大而方便,此刻笔者不必赘述,我也非常欠缺使用经验。

@extend

sass继承指的是让某个选择器继承另一个选择器的样式。关键在于扩展的目标类是否有效,其中的扩展机制能够保证以下几点:

  • 不会生成匹配不到任何元素的选择器

  • 它确保复杂的选择器是交错的,这样无论嵌套 HTML 元素的顺序如何,它们都可以工作。

  • 它尽可能地减少冗余选择器,同时仍然确保选择器的特性大于或等于被扩展的选择器的特性。

  • 它知道一个选择器何时匹配另一个选择器所做的一切,并可以将它们组合在一起。

  • 它智能地处理组合选择器、通用选择器和:not 选择器。

举个例子:


.content nav.sidebar

  @extend .info



// 不会扩展,因为“p”与“nav”不兼容。

p.info

  background-color: #dee9fc



// 没有办法知道.guide和.content的层级关系,因此Sass安全起见会生成两种选择器。

.guide .info

  border: 1px solid rgba(#000, 0.8)

  border-radius: 2px



// Sass知道每个匹配“main”的元素。也匹配“.content”。并避免生成不必要的交错选择器。

main.content .info

  font-size: 0.8em

最后的结果为:


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;

}

最后扩展的结果非常难以理解(对于我来说),在有必要的时候尽可能写让大多数人易于理解的扩展吧。

调试和信息

  • @error

  • @warn

  • @debug

如上所示,都用于在编译时输出信息或者在调试时检查数据。

参考

  • Sass 中文文档

  • SassMeister | The Sass Playground!

not-by-ainot-by-ai
已读 0%
文章推荐
文章封面
GitHub 故障 7 小时复盘:最隐蔽的重试风暴
更新于 2026-08-20
编程
文章封面
前端开发实战笔记:从 Axios 到 TanStack Query 的工程化实践
更新于 2025-09-01
编程
GitHubTwitter / XMediumDevJuejin

友情链接

Jimmy
Jimmy
老胡
老胡
Submara
Submara
Bruce Song
Bruce Song
Scarsu
Scarsu
宇阳
宇阳
Steven Lynn's Blog
Steven Lynn's Blog
OJ·Jimmy (Other Jimmy)
OJ·Jimmy (Other Jimmy)
liruifengv - Web 开发者,Astro 项目成员,开源爱好者。
liruifengv - Web 开发者,Astro 项目成员,开源爱好者。