Sass 基本介绍
[toc]
如果对本文有任何问题,建议,或者在前端技术体系方面有任何问题,可以添加我的微信: drylint , 我会尽可能为你解答,也会拉你进入前端技术进阶交流群,大家一起进步~
Sass 是 CSS 的超集,支持所有 css 语法,并在其基础上扩展。
Sass 支持像 css 一样的大括号语法,文件扩展名为 .scss
,以及另一种使用缩进的语法,文件扩展名为 .sass
。
教程主要采取完全兼容 css 的 SCSS 语法。
注释(Comments)
支持两种注释,分别是:
- 单行注释
// 注释文字
- 多行注释
/* 注释文字 */
单行注释(Single-line comments)
编译的时候会直接被忽略,不会编译到 CSS 中,所以也叫做“隐式注释”(silent comments)。
// 注释内容
多行注释(Multi-line comments)
编译时会将注释编译到 css 中,所以也叫做“显式注释”(loud comment)
// 这一行注释不会出现在编译的 css 中
/* 这一行会出现在编译的 css 中,除非是在压缩模式下则不会 */
/* 注释中还可以包含插值:
* 1 + 1 = #{1 + 1} */
/*! 这行注释即使在压缩模式下也会编译到 css 中 */
p /* 多行注释可以写在任何
* 允许空白出现的地方 */ .sans {
font-size: 16px;
}
编译后的 css:
/* 这一行会出现在编译的 css 中,除非是在压缩模式下则不会 */
/* 注释中还可以包含插值:
* 1 + 1 = 2 */
/*! 这行注释即使在压缩模式下也会编译到 css 中 */
p .sans {
font-size: 16px;
}
SassDoc
文档注释,类似于 jsdoc 。使用三斜线 ///
声明。
/// Computes an exponent.
///
/// @param {number} $base
/// The number to multiply by itself.
/// @param {integer (unitless)} $exponent
/// The number of `$base`s to multiply together.
/// @return {number} `$base` to the power of `$exponent`.
@function pow($base, $exponent) {
$result: 1;
@for $_ from 1 through $exponent {
$result: $result * $base;
}
@return $result;
}
特殊的函数(Special Functions)
- url()
- xxx
url()
url()
函数在CSS中很常用,但是它的语法与其他函数不同,它可以接受带引号的 url ,也可以接受不带引号的 url。因为未加引号的 URL 不是有效的 SassScript 表达式,所以 Sass 需要特殊的逻辑来解析它。
如果 url()
的参数是一个有效的无引号的 url ,Sass 会原样解析它,当然,插值也是可以用的。
如果参数不是一个有效的无引用的 url ,例如,如果它包含变量或函数调用,它将被解析为普通的 CSS 函数调用。
$roboto-font-path: "../fonts/roboto";
@font-face {
// This is parsed as a normal function call that takes a quoted string.
src: url("#{$roboto-font-path}/Roboto-Thin.woff2") format("woff2");
}
@font-face {
// This is parsed as a normal function call that takes an arithmetic
// expression.
src: url($roboto-font-path + "/Roboto-Light.woff2") format("woff2");
}
@font-face {
// This is parsed as an interpolated special function.
src: url(#{$roboto-font-path}/Roboto-Regular.woff2) format("woff2");
}
编译后的 css :
@font-face {
src: url("../fonts/roboto/Roboto-Thin.woff2") format("woff2");
}
@font-face {
src: url("../fonts/roboto/Roboto-Light.woff2") format("woff2");
}
@font-face {
src: url(../fonts/roboto/Roboto-Regular.woff2) format("woff2");
}
calc()
和 element()
calc()
和 element()
函数是在 CSS 规范中定义的。因为 calc() 的数学表达式与 Sass 的算法冲突,而 element()
的id可以被解析为颜色,所以它们需要特殊的解析。
Sass 允许任何文本出现在这些函数调用中,包括嵌套的圆括号。
除了可以使用插值来注入动态值会被编译处理。其他任何东西都不会被解释为 SassScript 表达式进行计算,而是原样输出。
progid:...()
和 expression()
弃用
expression()
和以 progid:
开头的函数是使用非标准语法的 Internet Explorer 遗留特性。尽管最近的浏览器已经不再支持它们,但是 Sass 继续解析它们以实现向后兼容。
min()
和 max()
CSS在 CSS Values and Units Level 4
中增加了对 min()
和 max()
函数的支持,Safari 很快就采用了它们来支持 iPhoneX 。
但是 Sass 在很久以前就已经有了自己的 min()
和 max()
函数,为了向后兼容所有现有的样式表。这就需要额外的句法技巧来实现。
如果一个 min()
或 max()
函数调用是有效的纯 CSS ,它将被编译为普通的 CSS 的 min()
或 max()
函数调用。
"纯CSS "包括嵌套调用 calc()
, env()
, var()
, min()
,或 max()
,以及插值。
但是,只要调用的时候包含了 SassScript 特性(如变量或函数调用),它就会被认为是对 Sass 自带的 min()
或 max()
函数的调用。
变量
在 Sass 中,声明变量必须以 $
开头。
$red: #f00;
div {
color: $red;
}
编译后的 css :
div {
color: #f00;
}
Sass 变量和 css 变量的区别:
Sass 变量会被编译成真实的值然后输出为 css ,也就是仅仅存在于开发阶段。
CSS 变量对于不同的元素可以有不同的值,但是 Sass 变量一次只有一个值。
Sass 变量是不可逆的,这意味着如果您使用了一个变量,然后在后面更改了它的值,那么之前的使用将依然保持不变。CSS 变量是声明性的,这意味着如果在后面更改了值,它将影响前面的使用和以后的使用。
注意:和所有的 Sass 标识符一样,Sass 变量将连字符 -
和下划线 _
视为相同的字符。这意味着 $font-size
和 $font_size
都指向同一个变量。这是 Sass 早期的历史遗留,当时它只允许在标识符名称中使用下划线。后来, Sass 增加了对连字符的支持,以匹配 CSS 的语法,sass 将这两个字符视为等效处理,以便于使迁移更加容易。
默认值
比如开发一个库,用户可以选择是否传递自定义的值,如果没有传递则使用默认值。
为了实现这一点,Sass 提供了 !default
标志。只有当变量没有定义或者它的值为 null
时,才会给该变量赋值。否则,将使用默认的值。
配置模块变量
用 !default
定义的变量,可以在使用 @use
规则加载模块时配置。
在模块中声明变量,并定义默认值:
// _library.scss
$black: #000 !default;
$border-radius: 0.25rem !default;
$box-shadow: 0 0.5rem 1rem rgba($black, 0.15) !default;
code {
border-radius: $border-radius;
box-shadow: $box-shadow;
}
在引用模块时,选择要自定义值的变量,忽略的变量则使用默认值:
// index.scss
@use 'library' with (
$black: #222,
$border-radius: 0.1rem
);
内置变量
内置模块定义的变量是无法被修改的。
比如,下面代码视图修改内置变量,但不会成功:
@use "sass:math" as math;
// This assignment will fail.
math.$pi: 0;
作用域
在 css 文件顶层声明的变量是全局变量,声明后可以在模块中的任何地方被访问。
在块({}
)中声明的变量是局部变量,只能在声明它们的块内访问。
// 全局变量
$red: #f00;
div {
// 局部变量
$black: #000;
color: $red;
}
p {
// 在这里引用局部变量编译时会报错
color: $black;
}
当局部变量和全局变量重名时,不会覆盖全局变量,而是同时存在,在哪个作用域访问的就是哪个变量。
$red: #f00;
div {
$red: #f55;
color: $red;
}
p {
color: $red;
}
编译后的 css :
div {
color: #f55;
}
p {
color: #f00;
}
如果想用一个局部变量去覆盖全局变量,也就是在块中修改全局变量的值,可以使用 !global
来完成:
$red: #f00;
div {
// !global 将修改全局变量的值,而不是在块中新建一个局部作用域
$red: #f55 !global;
color: $red;
}
p {
color: $red;
}
div {
color: #f55;
}
p {
color: #f55;
}
注意:如果使用 !global
的变量不是一个全局变量,则编译时会报错。
在流程控制语句(@if/@each/@for/@while
等)中声明的变量有一个自己的特殊作用域,它不会创建新变量去覆盖同级作用域中的同名变量,而是简单地进行原变量的赋值修改操作。
$dark-theme: true;
$red: #e55;
$black: #333;
@if $dark-theme {
$red: #f00;
$black: #000;
}
.button {
background-color: $red;
color: $black;
}
编译后的 css :
.button {
background-color: #f00;
color: #000;
}
在流程控制语句中,赋值给已经存在的变量则是修改操作,如果是不存在的变量则会创建一个新的变量,但这个新的变量也只能在这个流程控制语句的作用域中使用。
检测变量是否存在
Sass 核心库提供了两个用于处理变量的高级函数。meta.variable-exists()
函数返回给定名称的变量是否在当前作用域中存在, meta.global-variable-exists()
函数做同样的事情,但仅用于全局作用域。
@debug meta.variable-exists("var1"); // false
$var1: value;
@debug meta.variable-exists("var1"); // true
h1 {
// $var2 is local.
$var2: value;
@debug meta.variable-exists("var2"); // true
}
@debug meta.global-variable-exists("var1"); // false
$var1: value;
@debug meta.global-variable-exists("var1"); // true
h1 {
// $var2 is local.
$var2: value;
@debug meta.global-variable-exists("var2"); // false
}
用户有时可能会希望使用插值来定义基于另一个变量的变量名。Sass 不允许这样做,因为它使得我们很难一眼就知道哪些变量在哪里定义。但是,您可以做的是定义一个从名称到值的 map
,然后您可以使用变量访问该映射。
@use "sass:map";
$theme-colors: (
"success": #28a745,
"info": #17a2b8,
"warning": #ffc107,
);
.alert {
// Instead of $theme-color-#{warning}
background-color: map.get($theme-colors, "warning");
}
编译后的 css :
.alert {
background-color: #ffc107;
}
插值(Interpolation)
插值几乎可以在 Sass 样式表的任何地方使用,以将 SassScript 表达式的结果嵌入到 CSS 块中。
在 #{}
中放置一个表达式即可,比如可以用在:
- 选择器
- 属性名
- 自定义属性值
- CSS 的
@
语句中 @extends
- CSS
@imports
- 字符串
- 特殊函数
- CSS 函数名
- 保留注释(Loud comments )
/* ... */
下面展示部分用法,在选择器,属性,继承,注释语句中使用插值:
$selector: "hello";
$color: "color";
/* selector: #{$selector} */
.#{$selector} {
background-#{$color}: #f00;
}
.#{$selector}-2 {
@extend .#{$selector};
border-#{$color}: #f00;
}
/* selector: hello */
.hello,
.hello-2 {
background-color: #f00;
}
.hello-2 {
border-color: #f00;
}
在 SassScript 中,可以使用插值表达式将 SassScript 注入到未加引号的字符串中。这在动态生成名称(例如动画)或使用斜杠分隔值时特别有用。
注意: SassScript 中的插值永远返回一个未加引号的字符串,在上面的例子中已经看到了。
插值对于将值注入到字符串中很有用,但除此之外,在 SassScript 表达式中很少需要插值。
比如,使用变量完全不需要这样写: color: #{$red}
,而是可以直接使用变量: color: $red
。
注意:不应该使用插值插入数字。因为插值总是返回未加引号的字符串,返回值并不能进一步用于计算,这也同时避免了违反 Sass 内置的的安全保护规则,以确保能够正确使用单位。
Sass 有强大的单位运算,你可以使用它来代替。例如,与其写 #{$width}px
,不如写 $width * 1px
,或者更好的是,以px开头声明width` 已经有单位,你将得到一个很好的错误消息,而不是编译伪造的CSS。
还有,虽然可以使用插值将带引号的字符串转换为不带引号的字符串,但使用 string.unquote()
函数会更清楚。所以应该用 string.unquote($string)
来代替 #{$string}
。
@语句(At-Rules)
Sass 在 CSS 之上添加了新的 @
语句 :
@mixin
和@include
用于复用大的块级样式@function
声明自定义函数,用于 SassScript 表达式中@extend
用于在一个选择器中继承另一个选择器的样式@at-root
将代码块内部的样式编译到 css 最外层(相当于顶级作用域)@error
故意使编译失败而中断,并抛出错误信息@warn
抛出一条错误信息但不使编译程序失败而中断@debug
抛出一条用于 debug 调试的消息@if
,@each
,@for
,@while
流程控制语句
@mixin
and @include
@mixin
用于定义要复用的样式块,@include
用于调用这些样式块。
因历史遗留原因,mixin 的名字和 Sass 标识符一样,连字符(hyphens) -
和下划线(underscores)_
被视为完全相同。
定义 mixin 的语法:
// 不需要传参数时,复用固定的样式代码
@mixin {
// ...
}
// 或
// 需要使用时传递参数,动态复用样式代码
@mixin name(arg1, arg2, ..., argN) {
// ...
}
使用 mixin 的语法:
@include ;
// 或
@include (arg1, arg2, ...)
使用示例:
// a.scss
@mixin input {
padding: 10px;
color: #333;
}
@mixin button ($color, $fontSize) {
color: $color;
font-size: $fontSize;
}
@use "a";
.input {
@include a.input;
}
.button {
@include a.button(#333, 20px);
}
编译后的 css :
.input {
padding: 10px;
color: #333;
}
.button {
color: #333;
font-size: 20px;
}
通常情况下,如果一个 mixin 定义时有多少个参数,那么在调用时必须传递相同数量的参数,除非是定义 mixin 时使用了参数默认值。
mixin 参数默认值
定义一个参数默认值就像定义一个变量一样,参数名后加一个冒号,然后就可以写默认值了。
@mixin button ($color, $fontSize: 16px) {
color: $color;
font-size: $fontSize;
}
.button {
@include button(#f00);
}
编译后的 css :
.button {
color: #f00;
font-size: 16px;
}
默认参数值可以是任意 Sass 表达式,甚至是它前面的参数。
@mixin font ($size, $weight: if($size >= 24px, 600, 400)) {
font-size: $size;
font-weight: $weight;
}
.div1 {
@include font(16px);
}
.div2 {
@include font(24px);
}
编译后的 css :
.div1 {
font-size: 16px;
font-weight: 400;
}
.div2 {
font-size: 24px;
font-weight: 600;
}
关键词传参
默认情况下,调用 mixin 时传递的参数顺序必须和定义时的参数一一对应。
如果传递参数时指定参数关键词,则可以不按照定义的顺序来传参。
@mixin font ($weight, $size) {
font-size: $size;
font-weight: $weight;
}
.div1 {
@include font($size: 16px, $weight: 500);
}
编译后的 css :
.div1 {
font-size: 16px;
font-weight: 500;
}
注意,如果要传递不带关键词的参数,则它必须出现在关键词参数之前。
任意数量的参数
如果 mixin 的最后一个参数名以 ...
结尾,那么这个参数就可以接收传递过来的任意数量的参数,这个参数的值则会是一个列表。
@mixin order($height, $selectors...) {
@for $i from 0 to length($selectors) {
#{nth($selectors, $i + 1)} {
position: absolute;
height: $height;
margin-top: $i * $height;
}
}
}
@include order(150px, "input.name", "input.address", "input.zip");
编译后的 css :
input.name {
position: absolute;
height: 150px;
margin-top: 0;
}
input.address {
position: absolute;
height: 150px;
margin-top: 150px;
}
input.zip {
position: absolute;
height: 150px;
margin-top: 300px;
}
带有关键字的任意参数
如果调用 mixin 带了关键字,那么任意参数需要使用 meta.keywords()
来处理,处理后将返回一个 map 类型的数据。
如果没有将任意参数传递给 meta.keywords()
函数,那么这个任意参数列表就不允许接收带有关键词的参数,编译程序会报错。
@use "sass:meta";
@mixin syntax-colors($args...) {
@debug meta.keywords($args);
// (string: #080, comment: #800, variable: #60b)
@each $name, $color in meta.keywords($args) {
pre span.stx-#{$name} {
color: $color;
}
}
}
@include syntax-colors(
$string: #080,
$comment: #800,
$variable: #60b,
)
pre span.stx-string {
color: #080;
}
pre span.stx-comment {
color: #800;
}
pre span.stx-variable {
color: #60b;
}
传递任意参数
接收的任意参数可以是一个列表(list),那么,也可以把一个列表作为任意参数传递,同样只需要在后面加上 ...
即可。
$font: 16px, 600, #f00;
@include font($font...);
同样,也可以把一个 map
作为任意参数传递:
@mixin font ($size, $weight) {
font-size: $size;
font-weight: $weight;
}
$font: (
weight: 600,
size: 16px,
);
.div1 {
@include font($font...);
}
编译后的 css :
.div1 {
font-size: 16px;
font-weight: 600;
}
@content
样式块
除了接受参数之外,mixin 还可以接受整个样式块,称为内容块。
在 mixin 中,在样式块中写一个 @content
来声明这个位置接受一个内容块,传递一个样式块给 mixin,这个样式块的内容将会用来替换 @content
。
@mixin font ($size, $weight) {
font-size: $size;
font-weight: $weight;
@content;
}
$font: (
weight: 600,
size: 16px,
);
.div1 {
@include font($font...) {
font-family: sans-serif;
}
}
编译后的 css :
.div1 {
font-size: 16px;
font-weight: 600;
font-family: sans-serif;
}
可以书写多个 @content;
,这样将会编译生成多个接收到的样式块。
传递的样式块是有作用域限制的,只能访问样式块所处的位置的变量,而不能去访问 mixin 定义的作用域的变量。
如果要让样式块使用 mixin 定义的作用域的变量,则需要通过 @content()
传递给样式块。
使用 `@content 时传参
传参使用 @content(arg1, arg2, ...)
,接收使用 @include
@mixin font ($size, $weight) {
font-size: $size;
font-weight: $weight;
@content(#f00, $size * 2);
}
$font: (
weight: 600,
size: 16px,
);
.div1 {
@include font($font...) using ($color, $margin) {
font-family: sans-serif;
color: $color;
margin: $margin;
}
}
编译后的 css :
.div1 {
font-size: 16px;
font-weight: 600;
font-family: sans-serif;
color: #f00;
margin: 32px;
}
@content()
同样可以传递 list
或 map
类型的参数,用法和前面一样。
缩进语法的 mixin
缩进语法的 Sass 可以使用 =
来定义一个mixin,然后使用 +
来使用一个 mixin,但很不直观,不建议使用。
@at-root
通常用于嵌套的选择器中,在选择器前写下 @at-root
语句,用于将该选择器编译到样式表的最外层,而不是嵌套所在的位置。
.div1 {
color: #f00;
.div2 {
color: #0f0;
// 将 .div3 编译到最外层
@at-root .div3 {
color: #00f;
}
}
}
编译后的 css :
.div1 {
color: #f00;
}
.div1 .div2 {
color: #0f0;
}
.div3 {
color: #00f;
}
结合 mixin 来使用:
@use "sass:selector";
@mixin unify-parent($child) {
@at-root #{selector.unify(&, $child)} {
font-size: 16px;
@content;
}
}
.wrapper .field {
@include unify-parent("input") {
color: #f00;
}
@include unify-parent("select") {
color: #0f0;
}
}
编译后的 css :
.wrapper input.field {
font-size: 16px;
color: #f00;
}
.wrapper select.field {
font-size: 16px;
color: #0f0;
}
@at-root
还有另一种写法 @at-root { ... }
:
.div1 {
font-size: 16px;
@at-root {
.div2 {
color: #f00;
}
.div3 {
color: #0f0;
}
}
}
编译后的 css :
.div1 {
font-size: 16px;
}
.div2 {
color: #f00;
}
.div3 {
color: #0f0;
}
解决样式之外的东西
默认情况下, @at-root
只会解决普通样式规则, 其他像是 @media
或 @supports
等将会被丢掉。
使用 @at-root (with:
或 @at-root (without:
来告诉 Sass 在编译的时候是否包括一些指定的规则。
除了合法的 @
语句的名称,如 @media
中的 media
,还有两个特殊的值可以在查询中使用:
-
rule
指的是样式规则。例如,@at-root (with: rule)
不保留 @ 语句,但保留样式规则。 -
all
指所有 @语句 和 style 规则。
@media screen and (min-width: 900px) {
.page {
width: 100px;
@at-root (with: media) {
/* @at-root (with: media) */
.div1 {
font-size: 16px;
}
}
@at-root (without: media) {
.div2 {
/* @at-root (without: media) */
color: #111;
}
}
@at-root (with: rule) {
.div3 {
/* @at-root (with: rule) */
color: #111;
}
}
@at-root (without: rule) {
.div4 {
/* @at-root (without: rule) */
color: #111;
}
}
@at-root (with: all) {
.div5 {
/* @at-root (with: all) */
color: #111;
}
}
@at-root (without: all) {
.div6 {
/* @at-root (without: all) */
color: #111;
}
}
}
}
编译后的 css :
@media screen and (min-width: 900px) {
.page {
width: 100px;
}
/* @at-root (with: media) */
.div1 {
font-size: 16px;
}
}
.page .div2 {
/* @at-root (without: media) */
color: #111;
}
.page .div3 {
/* @at-root (with: rule) */
color: #111;
}
@media screen and (min-width: 900px) {
.div4 {
/* @at-root (without: rule) */
color: #111;
}
}
@media screen and (min-width: 900px) {
.page .div5 {
/* @at-root (with: all) */
color: #111;
}
}
.div6 {
/* @at-root (without: all) */
color: #111;
}