SCSS 3.5.5 简明上手指南

Sass 是成熟、稳定、强大的CSS 预处理器,截止到目前为止已经发展有 10 年,前当最新 release 版本为3.5.5。而SCSSSass3版本当中引入的新语法特性,完全兼容 CSS3 的同时继承了Sass强大的动态功能。本文翻译自《Sass Guide》《Sass Syntactically Awesome StyleSheets》两篇官方文档,讲解了现代化前端开发当中经常使用的 SCSS 语法特性,便于开发小组的同学快速上手。

基于 Gulp 完成前端自动化的年代,出于快速上手以及 npm 安装方便的考虑,开发团队一直沿用Lessgulp-less作为 CSS 预处理工具,但是 Sass 提供了更加丰富的动态语法特征,因此逐步淘汰基于 Gulp 的beaver前端项目脚手架以后,新项目全部基于 Webpack 的node-sasssass-loader作为预处理工具。Sass 和 Less 的详细比较可以参考sass-vs-lessSass 与 Less 比拼两篇文章,里面对两者的优劣做了非常详实的比较。

安装

如果正在使用的是 Linux 发行版,需要通过apt安装Ruby以及build-essential

1
2
sudo apt-get install ruby-dev
➜ gem install sass

命令行当中可以通过sass input.scss output.css编译 Scss 文件,也可以添加--watch选项监听 Scss 文件的变化:

1
➜  sass --watch input.scss:output.css

如果多个 Scss 文件放置在一个目录当中,也可以对整个文件目录进行监听。

1
➜  sass --watch app/sass:public/stylesheets

通过-i或者--interactive可以运行一个交互式的 SassScript 命令行。

1
2
3
➜  sass -i
>> 5px + 8px
13px

通过sass --help获取更多关于 SASS 在命令行的使用方法。

编码规则

SASS 首先会检查代码文件的 Unicode BOM(byte order mark),然后是@charset声明,再然后是底层运行 Ruby 的字符串编码,如果这些都未进行设置,将会默认以UTF-8编码输出 CSS 文件。

1
2
3
4
5
@charset 'utf-8';

#app {
background: url("../assets/背景图片.png");
}

建议代码开头位置显式定义@charset "encoding-name";,让 SASS 能够按照给定的字符集编译输出。

特性概览

CSS 书写代码规模较大的 Web 应用时,容易造成选择器、层叠的复杂度过高,因此推荐通过 SASS 预处理器进行 CSS 的开发,SASS 提供的变量嵌套混合继承等特性,让 CSS 的书写更加有趣与程式化。

变量

变量用来存储需要在 CSS 中复用的信息,例如颜色和字体。SASS 通过$符号去声明一个变量。

1
2
3
4
5
6
7
$font-stack: Helvetica, sans-serif;
$primary-color: #333;

body {
font: 100% $font-stack;
color: $primary-color;
}

上面例子中变量$font-stack$primary-color将会替换所有引用他们的位置。

1
2
3
4
body {
font: 100% Helvetica, sans-serif;
color: #333;
}

嵌套

SASS 允许开发人员以嵌套的方式使用 CSS,但是过度的使用嵌套会让产生的 CSS 难以维护,因此是一种不好的实践,下面的例子表达了一个典型的网站导航样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
nav {
ul {
margin: 0;
padding: 0;
list-style: none;
}

li {
display: inline-block;
}

a {
display: block;
padding: 6px 12px;
text-decoration: none;
}
}

大家注意上面代码中的ullia选择器都被嵌套在nav选择器当中使用,这是一种书写更高可读性 CSS 的良好方式,编译后产生的 CSS 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
nav ul {
margin: 0;
padding: 0;
list-style: none;
}
nav li {
display: inline-block;
}
nav a {
display: block;
padding: 6px 12px;
text-decoration: none;
}

引入

SASS 能够将代码分割为多个片段,并以 underscore 风格的下划线作为其命名前缀(_partial.scss),SASS 会通过这些下划线来辨别哪些文件是SASS 片段,并且不让片段内容直接生成为 CSS 文件,从而只是在使用@import指令的位置被导入。CSS 原生的@import会通过额外的 HTTP 请求获取引入的样式片段,而 SASS 的@import则会直接将这些引入的片段合并至当前 CSS 文件,并且不会产生新的 HTTP 请求。下面例子中的代码,将会在base.scss文件当中引入_reset.scss片断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// _reset.scss
html,
body,
ul,
ol {
margin: 0;
padding: 0;
}

// base.scss
@import "reset";
body {
font: 100% Helvetica, sans-serif;
background-color: #efefef;
}

SASS 中引入片断时,可以缺省使用文件扩展名,因此上面代码中直接通过@import 'reset'引入,编译后生成的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
html,
body,
ul,
ol {
margin: 0;
padding: 0;
}

body {
font: 100% Helvetica, sans-serif;
background-color: #efefef;
}

SASS 片断使用下划线前缀命名,主要用于SASS命令行工具watch 指定目录源码的场景;如果使用 Webpack 等打包工具则毋须顾虑该问题,CSS 样式将会通过 Webpack 加载器,按照 ES6 风格的import或 Webpack 插件extract-text-webpack-plugin进行打包和模块化。

混合

混合(Mixin)用来分组那些需要在页面中复用的 CSS 声明,开发人员可以通过向 Mixin 传递变量参数来让代码更加灵活,该特性在添加浏览器兼容性前缀的时候非常有用,SASS 目前使用@mixin name指令来进行混合操作。

1
2
3
4
5
6
7
8
9
10
@mixin border-radius($radius) {
border-radius: $radius;
-ms-border-radius: $radius;
-moz-border-radius: $radius;
-webkit-border-radius: $radius;
}

.box {
@include border-radius(10px);
}

上面的代码建立了一个名为border-radius的 Mixin,并传递了一个变量$radius作为参数,然后在后续代码中通过@include border-radius(10px)使用该 Mixin,最终编译的结果如下:

1
2
3
4
5
6
.box {
border-radius: 10px;
-ms-border-radius: 10px;
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
}

继承

继承是 SASS 中非常重要的一个特性,可以通过@extend指令在选择器之间复用 CSS 属性,并且不会产生冗余的代码,下面例子将会通过 SASS 提供的继承机制建立一系列样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 这段代码不会被输出到最终生成的CSS文件,因为它没有被任何代码所继承。
%other-styles {
display: flex;
flex-wrap: wrap;
}

// 下面代码会正常输出到生成的CSS文件,因为它被其接下来的代码所继承。
%message-common {
border: 1px solid #ccc;
padding: 10px;
color: #333;
}

.message {
@extend %message-common;
}

.success {
@extend %message-common;
border-color: green;
}

.error {
@extend %message-common;
border-color: red;
}

.warning {
@extend %message-common;
border-color: yellow;
}

上面代码将.message中的 CSS 属性应用到了.success.error.warning上面,魔法将会发生在最终生成的 CSS 当中。这种方式能够避免在 HTML 元素上书写多个class选择器,最终生成的 CSS 样式是下面这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.message,
.success,
.error,
.warning {
border: 1px solid #ccc;
padding: 10px;
color: #333;
}

.success {
border-color: green;
}

.error {
border-color: red;
}

.warning {
border-color: yellow;
}

操作符

SASS 提供了标准的算术运算符,例如+-*/%。在接下来的例子里,我们尝试在asidearticle选择器当中对宽度进行简单的计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
.container {
width: 100%;
}

article[role="main"] {
float: left;
width: 600px / 960px * 100%;
}

aside[role="complementary"] {
float: right;
width: 300px / 960px * 100%;
}

上面代码以960px为基准建立了简单的流式网格布局,SASS 提供的算术运算符让开发人员可以更容易的将像素值转换为百分比,最终生成的 CSS 样式如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
.container {
width: 100%;
}

article[role="main"] {
float: left;
width: 62.5%;
}

aside[role="complementary"] {
float: right;
width: 31.25%;
}

CSS 扩展

SASS 当中有两种可用的语法,一种是 Scss(即 SASS 化的 CSS),这是一种类 CSS 的语法,以.scss作为源码文件后缀。另一种是 Sass,提供了比较简明的 CSS 书写方式,通过缩进而非括号来标识嵌套的选择器,以及使用换行而非分号来分隔各个属性,并使用.sass作为代码文件的后缀。

Sass 和 Scss 两种代码风格可以通过sass-convert命令行工具进行相互转换:

1
2
3
➜  sass-convert test.scss test.sass

➜ sass-convert test.sass test.scss

日常开发环境下,出于前端开发的使用习惯,更多会选择 Scss 语法,接下来的内容也将会以 Scss 风格为主。

嵌套规则

Scss 允许 CSS 规则嵌套使用,父子规则将会呈现包含选择器的关系,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*===== SCSS =====*/
#main p {
color: #00ff00;
width: 97%;

.redbox {
background-color: #ff0000;
color: #000000;
}
}

/*===== CSS =====*/
#main p {
color: #00ff00;
width: 97%;
}
#main p .redbox {
background-color: #ff0000;
color: #000000;
}

这样可以避免重复的使用父级选择器,从而达到简化 CSS 代码结构的目的,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*===== SCSS =====*/
#main {
width: 97%;

p,
div {
font-size: 2em;
a {
font-weight: bold;
}
}

pre {
font-size: 3em;
}
}

/*===== CSS =====*/
#main {
width: 97%;
}
#main p,
#main div {
font-size: 2em;
}
#main p a,
#main div a {
font-weight: bold;
}
#main pre {
font-size: 3em;
}

引用父级选择器&

Scss 使用$关键字在 CSS 规则中引用父级选择器,例如在嵌套使用伪类选择器的场景下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*===== SCSS =====*/
a {
font-weight: bold;
text-decoration: none;
&:hover {
text-decoration: underline;
}
body.firefox & {
font-weight: normal;
}
}

/*===== CSS =====*/
a {
font-weight: bold;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
body.firefox a {
font-weight: normal;
}

无论 CSS 规则嵌套的深度怎样,关键字&都会使用父级选择器级联替换全部其出现的位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*===== SCSS =====*/
#main {
color: black;
a {
font-weight: bold;
&:hover {
color: red;
}
}
}

/*===== CSS =====*/
#main {
color: black;
}
#main a {
font-weight: bold;
}
#main a:hover {
color: red;
}

&必须出现在复合选择器开头的位置,后面再连接自定义的后缀,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*===== SCSS =====*/
#main {
color: black;
&-sidebar {
border: 1px solid;
}
}

/*===== CSS =====*/
#main {
color: black;
}
#main-sidebar {
border: 1px solid;
}

如果在父级选择器不存在的场景使用&,Scss 预处理器会报出错误信息。

嵌套属性

CSS 许多属性都位于相同的命名空间(例如font-familyfont-sizefont-weight都位于font命名空间下),Scss 当中只需要编写命名空间一次,后续嵌套的子属性都将会位于该命名空间之下,请看下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*===== SCSS =====*/
.demo {
// 命令空间后带有冒号:
font: {
family: fantasy;
size: 30em;
weight: bold;
}
}

/*===== CSS =====*/
.demo {
font-family: fantasy;
font-size: 30em;
font-weight: bold;
}

命令空间上可以直接书写CSS 简写属性,但是日常开发中建议直接书写CSS 简写属性,尽量保持 CSS 语法的一致性。

1
2
3
4
5
6
7
8
9
10
.demo {
font: 20px/24px fantasy {
weight: bold;
}
}

.demo {
font: 20px/24px fantasy;
font-weight: bold;
}

占位符选择器%

占位符选择器与id或者class选择器的用法类似,只是#.需要替换成为%,占位符选择器必须通过@extend指令进行调用。

关于占位符的讲解,请具体参考@extend章节。

注释

SASS 支持标准 CSS 的多行注释/* */和单行注释//,其中,多行注释会完整输出到编译后的 CSS 文件,而单行注释则不会。

1
2
3
4
5
6
7
8
9
10
/* 多行注释
* 会原样输出到CSS文件 */
body {
color: black;
}

// 单行注释不会出现在输出的CSS当中
a {
color: green;
}

编译成为 CSS 的结果如下:

1
2
3
4
5
6
7
8
9
10
@charset "UTF-8";
/* 多行注释
* 会原样输出到CSS文件 */
body {
color: black;
}

a {
color: green;
}

插值语句#{$variable}可以应用在多行注释当中。

1
2
$version: "3.5.5";
/* 这段CSS是通过SASS #{$version}生成的。*/

编译成为 CSS 的结果:

1
2
@charset "UTF-8";
/* 这段CSS是通过SASS 3.5.5生成的。*/

当注释中出现中文时,SASS 会自动在生成的 CSS 文件头部添加@charset "UTF-8";

SassScript

SassScript 是 Sass 提供的一系列 CSS 语法扩展,允许在任意 CSS 属性上书写变量、计算以及函数。SassScript 还可以通过interpolation插值生成选择器和属性名,这在编写mixins时非常有用。

变量$

SassScript 通过$关键字声明和使用一个变量。

1
2
3
4
5
6
7
8
9
10
11
/*===== SCSS =====*/
$width: 8rem; // 声明变量

#main {
width: $width; // 使用变量
}

/*===== CSS =====*/
#main {
width: 8rem;
}

SassScript 变量支持块级作用域,嵌套规则当中声明的变量都是局部变量,可以通过!global将局部变量声明为全局变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*===== SCSS =====*/
#menu {
$width: 5rem !global;
width: $width;
}

#sidebar {
width: $width;
}

/*===== CSS =====*/
#menu {
width: 5rem;
}

#sidebar {
width: 5rem;
}

数据类型

SassScript 支持 8 种数据类型,

  1. 数值 number1.2, 13, 10px
  2. 颜色 colorblue#04a3f9rgba(255, 0, 0, 0.5)
  3. 布尔值 bolleantruefalse
  4. 空值 nullnull
  5. 字符串 string:有或没有引号,"menu"'sidebar'navbar
  6. 数组 list:由空格或逗号分隔,1.5em 1em 0 2em, Helvetica, Arial, sans-serif
  7. 对象 map(key1: value1、key2: value2)
  8. 函数 function

Scss 支持所有其它类型的 CSS 属性值,例如 Unicode 字符或者!important声明。Scss 并没有对这些类型进行特殊处理,仅仅将其视为没有使用引号的字符串。

String

CSS 支持两种类型的字符串:

  1. 使用单双引号的:"Lucida Grande"'http://sass-lang.com';
  2. 没有使用引号的:sans-serifbold;

SassScript 能够自动识别这 2 种字符串,Scss 当中使用了哪种类型字符串,就会在生成的 CSS 文件原样输出该类型字符串。

但是这里存在一个意外,当使用#{}插值语法的时候,使用引号的字符串会失去引号,这样可以便于mixin中使用选择器的名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
/*===== SCSS =====*/
@mixin firefox-message($selector) {
body.firefox #{$selector}:before {
content: "Hi, Firefox users!";
}
}

@include firefox-message(".header");

/*===== CSS =====*/
body.firefox .header:before {
content: "Hi, Firefox users!";
}

List

SASS 当中可以通过 List 来表达 CSS 声明的值,例如margin: 10px 15px 0 0font-face: Helvetica, Arial, sans-serif。SASS 中的 List 是一系列由空格或者逗号分隔的值,实际上单个值也可以作为一个 List,即只拥有一项元素的 List。

List 本身的作用不多,但是通过 SassScript 的 List 函数可以让它发挥更大的作用。nth函数可以操作 List 中的每一项元素,join函数能够合并多个 List 在一起,append函数能够向 List 添加一项元素,而@each指令可以为 List 当中每项元素添加样式。

除了包含简单的值,List 还可以包含其它的 List,例如拥有 2 项元素的 List1px 2px, 5px 6px包含了1px 2px5px 6px两个 List,如果内部的 List 和外部 List 拥有相同的分隔符,那么必须使用圆括号()标识内部 List 的起始和结束位置,例如(1px 2px) (5px 6px)

当 List 被转换为普通 CSS 的时候,SASS 不会添加其它圆括号,这意味(1px 2px) (5px 6px)1px 2px 5px 6px变成 CSS 的时候结果是相同的。但是无论如何,它们在 SASS 里是不相同的,(1px 2px) (5px 6px)是一个拥有 2 个 List 元素的 List,1px 2px 5px 6px则是拥有 4 个普通元素的 List。

List 里同样可以不包含任何元素,这样的 List 会被表现为()同样可以认为其是一个空的 map),它们并不能直接输出为 CSS。例如 SASS 编译font-family: ()时将会发生错误。如果 List 包含一个空的 List()或者其本身为空值null,例如对于1px 2px () 3px1px 2px null 3px当中的null()将会在输出的 CSS 当中被移除。

逗号分隔的 List 尾部可能还跟着一个逗号,这在一些特殊的情况下比较有用,例如展现一个只有 1 个元素的 List,(1,)就是一个包含了元素1的 List,而(1 2 3,)是一个逗号分隔的 List,这个 List 又包含着一个空格分隔的 List,空格分隔的 List 拥有着123三个元素。

List 也可以通过方括号[]编写,方括号编写的 List用来在 CSS Grid 布局中表达行的名称,但是它同样也可以用于纯 SASS 代码,同样也可以通过逗号或者空格进行分隔。

Map

Map 提供了keyvalue之间的关联关系,并且能够通过key找到相应的value。他们可以容易的搜集value到一个被命名的分组,然后动态的操作这些分组。它们在 CSS 中没有直接并行的概念,尽管在句法上与媒体查询表达式相似:

1
$map: (key1: value1, key2: value2, key3: value3);

不同与前面提到的 List,Map 必须总是被圆括号()包裹,并且必须使用逗号,进行分隔。Map 中的keyvalue可以是任意的 SassScript 对象,一个 Map 可以只拥有 1 个给定的key和对应的value可以是一个 List),但是一个给定的value可能会关联到多个key

和 List 一样,Map 大部分情况下也需要通过 SassScript 函数对其进行操作。其中map-get函数负责查询 Map 中的valuemap-merge函数添加value至 Map,指令@each可以被用来为 Map 中的每个keyvalue添加样式。Map 中键值对的顺序总是与其被建立的时候相同。

List 能够使用的位置,Map 也能正常使用。当通过一个 List 函数进行使用的时候,Map 被视为拥有键值对的 List。例如:(key1: value1, key2: value2)在 List 函数当中将被视为一个嵌套的 List(key1 value1, key2 value2)。但是,值得注意的是,List 并不能相反的被视为一个 Map,尽管空的 List 将会抛出错误。()即可以被视为一个没有keyvalue的 Map,也可以视为没有元素项的 List.

注意 Map 的key可以是任意的 Sass 数据类型(即使是 Map),并且声明 Map 的语法允许任意的 SassScript 表达式,该表达式将会被解析以决定key的值。

Map 并不能被转换为普通 CSS,在 CSS 函数中使用它作为变量和参数的值将会导致错误。这种情况下,建议使用inspect($value)函数为 Map 的调试生成一个有用的字符串输出。

Color

任何 CSS 颜色表达式都返回一个 SassScript 色彩值,这包括大量被命名的颜色,它们与未引用的字符串是不可区分的。

在压缩输出模式下,Sass 将输出颜色的最小的 CSS 表示。例如,在压缩模式下,#FF0000将输出为红色red,而#FFEBCD将会输出为白杏色blanchedalmond

用户使用被命名的颜色时,经常会遇到一个问题:在。。。SASS 喜欢在其他输出模式中键入相同的输出格式。。。之后,一个颜色会被插值到一个选择器,代码压缩时这将会变成不合法的语法。要避免这个问题,在被命名颜色用于构建选择器的时候,总是需要为它们添加引号。

First-Class Function

get-function($function-name)将会返回一个函数引用,该函数能够被传递到call($function, $args...)并得到调用。First-Class 函数不能直接用于 CSS 输出,任何这样的尝试都将会引发错误。

运算

SassScript 所有数据类型都支持==或!=逻辑运算,但是每种数据类型还拥有自己的运算方式。

数值运算

数值类型支持常见的数值运算+-*/%,并在必要时在不同的绝对数值单位之间转换。SASS 数学函数会在计算过程中保留单位,这意味不同单位不能在一起进行运算(例如pxem),两个相同单位的数值相乘后可能会造成运算结果的单位重复,例如:10px * 10px == 100px * px,显然这里px * px是一个无效单位,此时 SASS 会由于使用非法单位而报错。

关系运算符<><=>=同样支持数值类型,而相等运算符==, !=则可以被 SASS 所有类型所支持。

除法与标识符/

CSS 允许在属性值中使用/作为分隔数字的手段,SassScript 为 CSS 属性语法提供了一个扩展,允许/作为除法运算符来使用。如果 SassScript 当中 2 个数值通过/进行分隔,那么它们的计算结果将会出现在生成的 CSS 当中。

/被解释为除法的情况主要有以下 3 种:

  1. 如果值及其部分被保存到一个变量,或者通过一个函数返回。
  2. 值被圆括号()包裹,除非这些圆括号在一个 List 的外面并且该地,
  3. 如果指定值被用于其它算术表达式。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*===== SCSS =====*/
p {
font: 10px/8px; // 普通CSS,未进行除法运算。
$width: 1000px;
width: $width/2; // 使用一个变量,进行了除法运算。
width: round(1.5) / 2; // 使用一个函数,进行了除法运算。
height: (500px/2); // 使用圆括号, 进行了除法运算。
margin-left: 5px + 8px/2px; // 使用了加号+, 进行了除法运算。
font: (italic bold 10px/8px); // 在List当中,圆括号不会进行计算。
}

/*===== CSS =====*/
p {
font: 10px/8px;
width: 500px;
height: 250px;
margin-left: 9px;
}

如果需要在普通 CSS 当中使用变量,可以通过#{}去插入它们,例如:

1
2
3
4
5
6
7
8
9
10
11
/*===== SCSS =====*/
p {
$font-size: 12px;
$line-height: 30px;
font: #{$font-size}/#{$line-height};
}

/*===== CSS =====*/
p {
font: 12px/30px;
}
减法、负数与标识符-

中划线-在 CSS 和 SASS 中有许多不同的意义,可以表达一个减法运算符(5px - 3px),一个负数的开始(-23px),一元负值运算符(-$var),或者一个标识符的组成部分(font-weight),大部分情况下何时何地使用是比较清晰的,但也存在一些例外情况,为了避免这些例外需要遵循如下原则:

  1. 进行减法运算的时候,符号两则尽量保留空格。
  2. 作为负值标识或一元负值运算符时,需要在数值或变量前包含一个空格。
  3. 如果是在空格分隔的 List 中,就用括号将一元负值运算符括起来,就像10px (-$var)一样。

不同含义的表达遵循如下的优先次序:

  • 表达式中使用字母时,a-1表示一个没有引号的字符串,其值为"a-1"。但是唯一的例外在于单位的使用,比如5px-3px5px - 3px是等效的,其执行结果为2px
  • 两个数字之间没有空格意味着这是一个减法,所以1-21 - 2作用相同。
  • -放置在数字开头表示这是一个负数,所以1 -2是一个包含了1-2两个元素的 List。
  • 两个数字之间不考虑空格表示这是减法,所以1 -$var1 - $var相同。
  • 放置在一个值的前面表示这是一元负值运算符,该运算符会返回当前数值的负值。
1
2
3
4
5
6
7
8
9
➜  hank ✗ sass -i
>> a-1
"a-1"
>> 5px-3px
2px
>> 1 -2
(1 -2)
>> 1-2
-1

颜色运算

SASS 中所有的数学运算都分段的支持颜色值,即分别对绿依次进行计算。

1
2
3
4
/*===== SCSS =====*/
p {
color: #010203 + #040506;
}

在执行完01 + 04 = 0502 + 05 = 0703 + 06 = 09运算后编译为下面的 CSS:

1
2
3
4
/*===== CSS =====*/
p {
color: #050709;
}

通常来说,使用颜色函数比使用颜色运算能更加有效的达到相同目的。颜色运算同样也可以作用于数值与颜色值,并且依然是分段进行的,例如:

1
2
3
p {
color: #010203 * 2;
}

进行01 * 2 = 0202 * 2 = 0403 * 2 = 06运算后编译为下面 CSS:

1
2
3
p {
color: #020406;
}

注意,带有 alpha 通道的颜色(使用rgbahsla函数创建的颜色)必须具有相同的 alpha 值单位,以便与其执行颜色运算。

1
2
3
4
5
6
7
8
9
/*===== SCSS =====*/
p {
color: rgba(255, 0, 0, 0.75) + rgba(0, 255, 0, 0.75);
}

/*===== CSS =====*/
p {
color: rgba(255, 255, 0, 0.75);
}

颜色的 alpha 通道可以通过opacifytransparentize函数进行修改,例如:

1
2
3
4
5
6
7
8
9
10
11
12
/*===== SCSS =====*/
$translucent-red: rgba(255, 0, 0, 0.5);
p {
color: opacify($translucent-red, 0.3); // 增加alpha通道值
background-color: transparentize($translucent-red, 0.25); // 减少alpha通道值
}

/*===== CSS =====*/
p {
color: rgba(255, 0, 0, 0.8);
background-color: rgba(255, 0, 0, 0.25);
}

IE 的 filter 滤镜要求所有颜色都包括 alpha 层,并且严格采用#AABBCCDD格式,SASS 提供了ie_hex_str函数能够更加简便的对颜色值进行转换,例如:

1
2
3
4
5
6
7
8
9
10
11
/*===== SCSS =====*/
$translucent-red: rgba(255, 0, 0, 0.5);
$green: #00ff00;
div {
filter: progid:DXImageTransform.Microsoft.gradient(enabled='false', startColorstr='#{ie-hex-str($green)}', endColorstr='#{ie-hex-str($translucent-red)}');
}

/*===== CSS =====*/
div {
filter: progid:DXImageTransform.Microsoft.gradient(enabled='false', startColorstr='#FF00FF00', endColorstr='#80FF0000');
}

字符串运算

SassScript 使用+运算符执行字符串连接操作。

1
2
3
4
5
6
7
8
9
/*===== SCSS =====*/
p {
cursor: e + -resize;
}

/*===== CSS =====*/
p {
cursor: e-resize;
}

注意,如果有引号的字符串被添加到没有引号的字符串(即带引号的字符串在+的左侧),生成的字符串将会是有引号的。同理,如果将没有引号的字符串添加到有引号的字符串(即没有引号的字符串在+的左侧),则结果为没有引号的字符串。

1
2
3
4
5
6
7
8
9
10
11
/*===== SCSS =====*/
p:before {
content: "Foo " + Bar; // 有引号字符串 + 无引号字符串 = 有引号字符串
font-family: sans- + "serif"; // 无引号字符串 + 有引号字符串 = 无引号字符串
}

/*===== CSS =====*/
p:before {
content: "Foo Bar";
font-family: sans-serif;
}

默认情况下,临近的两个 CSS 属性值可以通过空格进行连接。

1
2
3
4
5
6
7
8
9
/*===== SCSS =====*/
p {
margin: 3px + 4px auto;
}

/*===== CSS =====*/
p {
margin: 7px auto;
}

字符串里可以通过#{}插入动态的值,就像下面这样:

1
2
3
4
5
6
7
8
9
/*===== SCSS =====*/
p:before {
content: "I ate #{5 + 10} pies!";
}

/*===== CSS =====*/
p:before {
content: "I ate 15 pies!";
}

如果字符串插值#{}中的变量是空值,则插值表达式的结果将被视为空字符串。

1
2
3
4
5
6
7
8
9
10
/*===== SCSS =====*/
$value: null; // 空值
p:before {
content: "I ate #{$value} pies!";
}

/*===== CSS =====*/
p:before {
content: "I ate pies!";
}

布尔运算

SassScript 当中布尔类型的值支持与and或or非not运算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*===== SCSS =====*/
$year: 2018;
$moth: 8;
$day: 6;
$name: Hank;
#app {
@if ($year > 2010 and $moth==8 or $day==6 not $name==Hank) {
color: blue;
} @else {
color: red;
}
}

/*===== CSS =====*/
#app {
color: blue;
}

List 运算

数组不支持任何特定的计算方式,只能通过list 函数进行操作,下面代码是一个使用了hsl($hue, $saturation, $lightness)函数的例子:

1
2
3
4
5
6
7
8
9
/*===== SCSS =====*/
a {
color: hsl(120deg, 100%, 50%);
}

/*===== CSS =====*/
a {
color: lime;
}

圆括号

SASS 中可以通过圆括号{}来调整运算的优先级。

1
2
3
4
5
6
7
8
9
/*===== SCSS =====*/
p {
width: 1em + (2em * 3);
}

/*===== CSS =====*/
p {
width: 7em;
}

函数

SassScript 内置了许多有用的函数,可以通过普通的 CSS 语句进行调用。

1
2
3
4
5
6
7
8
9
/*===== SCSS =====*/
p {
color: hsl(0, 100%, 30%);
}

/*===== CSS =====*/
p {
color: #990000;
}

关键词参数

Sass 函数还允许通过关键词参数(即带有键名的参数)进行调用,上面的例子也可以改写为下面代码:

1
2
3
4
5
6
7
8
9
/*===== SCSS =====*/
p {
color: hsl($hue: 0, $saturation: 100%, $lightness: 30%);
}

/*===== CSS =====*/
p {
color: #990000;
}

虽然这不是很简洁,但可以使样式表更加容易阅读。同时还能够让函数提供更灵活的接口,提供多个参数的同时,并不会变得难以调用。

命名参数能够以任何顺序进行传递,而带有默认值的参数可以省略掉。如果命名参数是变量名,那么下划线与破折号可以互换使用。

可以通过Sass::Script::Functions查看 SASS 函数及其参数名称的完整清单,也可以通过 Ruby 进行自定义。

插值#{}

开发人员可以通过插值interpolation#{}在选择器和属性名称中使用 SassScript 变量。

1
2
3
4
5
6
7
8
9
10
11
/*===== SCSS =====*/
$name: uinika;
$attr: border;
p.#{$name} {
#{$attr}-color: blue;
}

/*===== CSS =====*/
p.uinika {
border-color: blue;
}

还可以使用#{}将 SassScript 放入属性值,大多数情况下这种方式并不优于变量,但是使用#{}意味着其附近的任意操作都将被视为普通 CSS,例如:

1
2
3
4
5
6
7
8
9
10
11
/*===== SCSS =====*/
p {
$font-size: 12px;
$line-height: 30px;
font: #{$font-size}/#{$line-height};
}

/*===== CSS =====*/
p {
font: 12px/30px;
}

SassScript 中的&

如同使用选择器一样,SassScript 中的&引用着当前的父选择器列表一个由逗号分隔 List 作为元素再通过空格进行分隔的 List)。

1
2
3
4
.navbar.text .sidebar.word,
.menu.item {
$selector: &;
}

上面代码中$selector的值为((".navbar.text" ".sidebar.word"), ".menu.item"),这里的复合选择器使用了引号"标识它是一个字符串,但是真实情况它可能是没有引号的。即使其父选择器并没有包含逗号和空格,&依然会拥有两层嵌套,因此它能够被持续的访问。

父级选择器列表不存在的时候,&运算符的值为null,使用mixin当中可以通过该特性判断父选择器列表是否存在。

1
2
3
4
5
6
7
8
9
10
11
@mixin does-parent-exist {
@if & {
&:hover {
color: red;
}
} @else {
a {
color: red;
}
}
}

变量默认值!default

可以在变量后添加!default声明,如果变量已被赋值就不会再被重新进行赋值,如果变量未被赋值,则会被赋予新值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*===== SCSS =====*/
$content: "First content";
$content: "Second content?" !default;
$new_content: "First time reference" !default;
#main {
content: $content;
new-content: $new_content;
}

/*===== CSS =====*/
#main {
content: "First content";
new-content: "First time reference";
}

如果变量的值是null,则会被!default视为没有赋值。

1
2
3
4
5
6
7
8
9
10
11
/*===== SCSS =====*/
$content: null;
$content: "Non-null content" !default;
#main {
content: $content;
}

/*===== CSS =====*/
#main {
content: "Non-null content";
}

@规则与指令

SASS 支持所有 CSS3 的@-Rules规则,以及 SASS 特有的#Directive指令。

@import / #import

SASS 拓展了 CSS 的@import允许其导入.scss.sass文件,导入的文件将合并、编译到一个 CSS 文件,文件中的变量和 mixin 都可以在导入的主文件当中使用。

SASS 会基于当前目录查找其它文件,以及 Rack、Rails 或者 Merb 下的 SASS 文件目录。可以通过:load_paths--load-path选项指定额外的搜索目录。

@import通过指定路径以及文件名来引入 SASS 文件,但是在下面 4 种情况当中,@import仅被编译为普通的 CSS 语句:

  1. 如果文件拓展名为.css
  2. 如果文件名以http://开头。
  3. 如果文件名是url()
  4. 如果@import包含媒体查询语句。
1
2
3
4
5
6
7
8
9
10
11
/*===== SCSS =====*/
@import "app.css";
@import "pp" screen;
@import "https://uinika.github.io/app";
@import url(app);

/*===== CSS =====*/
@import url(app.css);
@import "pp" screen;
@import "https://uinika.github.io/app";
@import url(app);

除上述四种情况外,文件拓展名为.scss.sass的情况都会进行预编译处理,即使文件拓展名缺省依然能够正确的进行导入。

1
2
3
// 下面2种导入方式等效
@import 'base';
@import 'base.scss;

SASS 允许在一个@import语句内同时导入多个文件。

1
@import "base", "reset", "app";

@import语句内可以有限制的使用#{}插值,即不能动态的导入基于变量的 SASS 文件,只能用于标准 CSS 的@import url("");导入方式。

1
2
3
4
5
6
/*===== SCSS =====*/
$family: unquote("Droid+Sans");
@import url("http://fonts.googleapis.com/css?family=#{$family}");

/*===== CSS =====*/
@import url("http://fonts.googleapis.com/css?family=Droid+Sans");

Partial

如果你引入一个 SCSS 或 Sass 文件,但并不希望它们编译成 CSS 文件,那么可以在文件名开头添加一个下划线_,这样 Sass 将不会把该文件编译成 CSS 文件,然后引入文件的时候不需要添加下划线。例如:你拥有一个_colors.scss,但是 Sass 并不会创建_colors.css文件,然后,不过仍然可以通过@import "colors";语句引入_colors.scss文件。

注意,不能在同一目录中包含相同名称的 Partial 和非 Partial;例如:_colors.scss不能与colors.scss在相同目录下并存。

嵌套的@import

大多数情况下,在代码的顶层使用@import导入是最为常用,但是我们依然可以在CSS规则@media规则中使用;其效用如同基本的@import,仍然会去包含引入文件的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*===== SCSS =====*/
// 文件demo.scss包含如下代码
.demo {
color: red;
}

#app {
@import "demo"; // 在文件app.scss中引入demo.scss
}

/*===== CSS =====*/
#app .demo {
color: red;
}

指令只能用于文档的基本级别,在一个嵌套上下文当中被@import引入的文件里,诸如@mixin或者@charset是不被允许的。

不能在mixin或者控制指令当中嵌套使用@import

@media / #media

Sass 中@media的用法与 CSS 相同,但是允许在 CSS 规则中嵌套使用。编译时@media将被编译到文件最外层,包含嵌套的父级选择器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*===== SCSS =====*/
.sidebar {
width: 300px;
@media screen and (orientation: landscape) {
width: 500px;
}
}

/*===== CSS =====*/
.sidebar {
width: 300px;
}

@media screen and (orientation: landscape) {
.sidebar {
width: 500px;
}
}

@media的查询条件可以嵌套使用,编译后 SASS 会自动添加and关键字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*===== SCSS =====*/
@media screen {
.sidebar {
@media (orientation: landscape) {
width: 500px;
}
}
}

/*===== CSS =====*/
@media screen and (orientation: landscape) {
.sidebar {
width: 500px;
}
}

@media的查询条件当中也可以使用 SassScript。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*===== SCSS =====*/
$media: screen;
$feature: -webkit-min-device-pixel-ratio;
$value: 1.5;
@media #{$media} and ($feature: $value) {
.sidebar {
width: 500px;
}
}

/*===== CSS =====*/
@media screen and (-webkit-min-device-pixel-ratio: 1.5) {
.sidebar {
width: 500px;
}
}

@extend / #extend

现实的 Web 页面开发场景当中,当一个class属性拥有其它多个class所具备样式的时候,通常的做法是在 HTML 上书写全部这些class,如同下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
<div class="error seriousError">
Oh no! You've been hacked!
</div>

<style>
.error {
border: 1px #f00;
background-color: #fdd;
}
.seriousError {
border-width: 3px;
}
</style>

这样的代码并不利于维护,并且会带来非语义相关的样式到 HTML 上。

使用 SASS 提供的@extend可以在不同 CSS 选择器之间继承样式,从而完美避免上述问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*===== SCSS =====*/
.error {
border: 1px #f00;
background-color: #fdd;
}
.seriousError {
@extend .error;
border-width: 3px;
}

/*===== CSS =====*/
.error,
.seriousError {
border: 1px #f00;
background-color: #fdd;
}
.seriousError {
border-width: 3px;
}

上面代码中.error的样式会自动应用到.seriousError,作用相当于同时使用了这两个class上的样式。

通过.error定义的其它样式规则,同样会在.seriousError上工作,比如下面这样定义样式后。

1
2
3
.error.danger {
background-image: url("/image/warning.png");
}

然后<div class="seriousError danger">的背景图片同样会是warning.png

工作原理

@extend是通过向被继承的 CSS 选择器,插入需要继承的 CSS 选择器来进行工作,例如上面例子的代码转换如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*===== SCSS =====*/
.error {
border: 1px #f00;
background-color: #fdd;
}
.error.danger {
background-image: url("/image/warning.png");
}
.seriousError {
@extend .error;
border-width: 3px;
}

/*===== CSS =====*/
.error,
.seriousError {
border: 1px #f00;
background-color: #fdd;
}

.error.danger,
.seriousError.danger {
background-image: url("/image/warning.png");
}

.seriousError {
border-width: 3px;
}

合并选择器的时候,@extend将会避开不必要的样式规则重复,并且不会生成不能匹配任何东西的选择器。

继承复杂的选择器

class选择器并不是唯一能被继承的选择器,其它选择器也可以被继承,甚至可以去继承一个 CSS 元素,接着看下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
/*===== SCSS =====*/
.hoverlink {
@extend a:hover;
}
a:hover {
text-decoration: underline;
}

/*===== CSS =====*/
a:hover,
.hoverlink {
text-decoration: underline;
}

就像前面内容中的.error.intrusion一样,a:hover上定义的规则也会在.hoverlink上工作,即使它们拥有着其它的选择器。

1
2
3
4
5
6
7
8
9
10
11
12
13
/*===== SCSS =====*/
.hoverlink {
@extend a:hover;
}
.comment a.user:hover {
font-weight: bold;
}

/*===== CSS =====*/
.comment a.user:hover,
.comment .user.hoverlink {
font-weight: bold;
}

多重继承

一个选择器可以同时继承多个选择器的样式,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*===== SCSS =====*/
.error {
border: 1px #f00;
background-color: #fdd;
}
.attention {
font-size: 3em;
background-color: #ff0;
}
.seriousError {
@extend .error;
@extend .attention;
border-width: 3px;
}

/*===== CSS =====*/
.error,
.seriousError {
border: 1px #f00;
background-color: #fdd;
}

.attention,
.seriousError {
font-size: 3em;
background-color: #ff0;
}

.seriousError {
border-width: 3px;
}

上面代码中,每个.seriousError旁边都会伴随.error.attention选择器。通常,文档后面定义的样式将会采取这样的优先级规则:.seriousError的背景色是#ff0而非#fdd,因为.attention定义的位置比.error更加靠后.

多重继承时还可以使用逗号,分隔的选择器列表,例如:@extend .error, .attention作用与@extend .error; @extend .attention;等效。

链式继承

一个选择器可以继承另外一个选择器,另外一个选择器又继承了第三个选择器,从而形成链式的继承结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/*===== SCSS =====*/
.error {
border: 1px #f00;
background-color: #fdd;
}
.seriousError {
@extend .error;
border-width: 3px;
}
.criticalError {
@extend .seriousError;
position: fixed;
top: 10%;
bottom: 10%;
left: 10%;
right: 10%;
}

/*===== CSS =====*/
.error,
.seriousError,
.criticalError {
border: 1px #f00;
background-color: #fdd;
}

.seriousError,
.criticalError {
border-width: 3px;
}

.criticalError {
position: fixed;
top: 10%;
bottom: 10%;
left: 10%;
right: 10%;
}

上面代码中,所有使用.error的位置也会使用.seriousError,同时使用.seriousError.error的位置则都会使用到.criticalError

选择器序列

诸如.foo .bar or .foo + .bar这样的选择器队列目前还不能被继承,但是嵌套选择器本身是可以使用@extend实现继承的,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*===== SCSS =====*/
a {
color: blue;
&:hover {
text-decoration: underline;
}
}

#fake-links .link {
@extend a;
}

/*===== CSS =====*/
a,
#fake-links .link {
color: blue;
}
a:hover,
#fake-links .link:hover {
text-decoration: underline;
}

有时候选择器序列会继承出现在另一个序列中的选择器,这种情况下就需要合并两个序列,例如:

1
2
3
4
5
6
7
/*===== SCSS =====*/
#admin .tabbar a {
font-weight: bold;
}
#demo .overview .fakelink {
@extend a;
}

由上面代码在技术上可能匹配 2 个选择器序列当中的每个序列,可能导致生成的 CSS 样式体积较大。因此如果例子中需要 10 个选择器,Sass 将会只会生成一个有效的选择器。

当被合并的 2 个选择器序列没有共同的选择器时,那么将会生成 2 个新的选择器:第 1 个选择器序列在第 2 个之前,以及第 2 个选择器序列在第 1 个之前,就像下面这样:

1
2
3
4
5
6
/*===== CSS =====*/
#admin .tabbar a,
#admin .tabbar #demo .overview .fakelink,
#demo .overview #admin .tabbar .fakelink {
font-weight: bold;
}

如果需要合并的 2 个选择器序列共享了部分选择器,那么这些共享的选择器将会被合并,只有不同的选择器会发生变化。下面的例子中,两个选择器序列都包含#admin,所以编译结果会合并这两个 ID 选择器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*===== SCSS =====*/
#admin .tabbar a {
font-weight: bold;
}
#admin .overview .fakelink {
@extend a;
}

/*===== CSS =====*/
#admin .tabbar a,
#admin .tabbar .overview .fakelink,
#admin .overview .tabbar .fakelink {
font-weight: bold;
}

只能@extend 的选择器

有些时候,你只想为需要去@extend继承的 Class 选择器编写样式,并且不希望直接使用到 HTML。在编写 Sass 库的时候,你可能要为使用者提供@exnted继承的样式,并且使用者如果不需要可以选择忽略。

如果使用普通的 Class 选择器,会生成更多额外的 CSS。而且可能与其它 Class 选择器发生样式冲突,因此 Sass 提供了占位选择器placeholder selectors),例如%foo

占位选择器的外观看起来与 Class 或者 ID 选择器类似,只是#.%所替代。占位选择器可以出现在任意 Class 和 ID 选择器能够使用的位置,并能够防止其中定义的规则集被渲染为 CSS。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*===== SCSS =====*/
// This ruleset won't be rendered on its own.
#context a%extreme {
color: blue;
font-weight: bold;
font-size: 2em;
}
.notice {
@extend %extreme;
}

/*===== CSS =====*/
#context a.notice {
color: blue;
font-weight: bold;
font-size: 2em;
}

!optional

当继承一个选择器的时候,如果@extend没有工作将会发生错误,例如对于a.important {@extend .notice},如果当前文档没有包含.notice选择器就会发生错误。如果被包含的这个选择器.noticeh1.notice,由于h1a具有冲突,因此没有新的选择器将会被创建。

如果希望@extend不生成任何选择器,可以在选择器后面添加!optional标志,例如:

1
2
3
a.important {
@extend .notice !optional;
}

在指令中使用@extend

@media之类的指令中使用@extend会存在一些限制,Sass 不能让@media代码块之外的 CSS 规则应用到其内部的选择器,从而避免四处拷贝样式并新建大量的样式块。这意味如果在@media或其它 CSS 指令中使用@extend,那么就只能继承当前指令块内出现的选择器。

例如下面的代码工作状态良好:

1
2
3
4
5
6
7
8
9
10
@media print {
.error {
border: 1px #f00;
background-color: #fdd;
}
.seriousError {
@extend .error;
border-width: 3px;
}
}

但是这样的代码就是错误的:

1
2
3
4
5
6
7
8
9
10
11
12
.error {
border: 1px #f00;
background-color: #fdd;
}

@media print {
.seriousError {
// 无效的继承,因为.error定义在@media print指令的外面
@extend .error;
border-width: 3px;
}
}

希望有一天浏览器能够原生支持@extend,并允许它用于@media或其它指令当中。

@at-root / #at-root

某些需要放置在文档根元素上的样式,可以就近的放置在其父选择器上,该特性同样可用于行内 CSS 选择器。

1
2
3
4
5
6
7
8
9
/*===== SCSS =====*/
.parent {
...
@at-root .child { ... }
}

/*===== CSS =====*/
.parent { ... }
.child { ... }

@at-root可以通过代码块同时包含多个选择器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*===== SCSS =====*/
.parent {
...
@at-root {
.child1 { ... }
.child2 { ... }
}
.step-child { ... }
}

/*===== CSS =====*/
.parent { ... }
.child1 { ... }
.child2 { ... }
.parent .step-child { ... }

@at-root默认情况下仅仅是排除掉父级选择器,但是实际开发过程中也有可能需要将@media之类的指令选择性的过滤、移动到外面,这里可以通过@at-root (without: ...)@at-root (with: ...)语法实现该操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*===== SCSS =====*/
@media print {
.page {
width: 8in;
@at-root (without: media) {
color: red;
}
}
}

/*===== CSS =====*/
@media print {
.page {
width: 8in;
}
}

.page {
color: red;
}

可以向@at-root传递 2 个特殊值,rule表示普通的 CSS 规则,因此@at-root (without: rule)作用类似于没有任何查询条件的@at-rootall代码全部 CSS 规则,@at-root (without: all)代表样式将会被移动到所有指令和 CSS 规则的外侧。

@debug

@debug指令打印 SassScript 表达式的值到标准错误输出流,可以用来调试具有复杂 SassScript 的 SASS 文件,例如:

1
2
3
4
5
/*===== SCSS =====*/
@debug 10em + 12em;

/*===== CSS =====*/
Line 1 DEBUG: 22em

@warn

@warn指令打印 SassScript 表达式的值到标准错误输出流,在警告用户弃用库或者修复mixin轻微错误的场景比较有用,@warn@debug存在 2 个主要区别:

可以使用--quiet命令行选项或 SASS 的:quiet选项来关闭@warn警告。 样式表 trace 将与消息一起被打印,开发人员可以方便的看到@warn警告发生在样式表的哪里个位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*===== SCSS =====*/
@mixin adjust-location($x, $y) {
@if unitless($x) {
@warn "Assuming #{$x} to be in pixels";
$x: 1px * $x;
}
@if unitless($y) {
@warn "Assuming #{$y} to be in pixels";
$y: 1px * $y;
}
position: relative;
left: $x;
top: $y;
}

@error

@error指令抛出一个 SassScript 表达式的值作为致命错误,其中包含了友好的堆栈 trace,可以用来验证mixinfunction的参数,例如:

1
2
3
4
5
6
7
8
9
10
11
@mixin adjust-location($x, $y) {
@if unitless($x) {
@error "$x may not be unitless, was #{$x}.";
}
@if unitless($y) {
@error "$y may not be unitless, was #{$y}.";
}
position: relative;
left: $x;
top: $y;
}

SASS 当前还没有提供捕获@error的方式。

控制指令和表达式

SassScript 支持一些基本的控制指令和表达式,例如只在某些条件下包含样式,或是在多次变化中包含相同的样式。

控制指令属于高级特性,并不经常使用,主要用于mixins当中,尤其是对于 Compass 这样的库。

if()

内置的if()函数允许在一个条件处理分支返回两种可能的结果,它可以用于任意的脚本上下文。if()函数只能判断相应的那个参数并且返回,这允许引用已经被定义或者计算的变量,否则将会导致错误发生(例如除以零)。

1
2
3
4
5
6
7
8
9
10
11
/*===== SCSS =====*/
div {
width: if(true, 1px, 2px);
height: if(false, 1px, 2px);
}

/*===== CSS =====*/
div {
width: 1px;
height: 2px;
}

@if

@if指令接收一个 SassScript 表达式,当表达式返回false或者null之外的数据时,会选择使用接下来的嵌套语句,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*===== SCSS =====*/
p {
@if 1+1==2 {
border: 1px solid;
}
@if 5 < 3 {
border: 2px dotted;
}
@if null {
border: 3px double;
}
}

/*===== CSS =====*/
p {
border: 1px solid;
}

@if语句后面可以跟随多个@else if语句和一个@else语句,如果@if语句失败,SASS 将逐个尝试@else if语句,直至其中一个成功;如果全部失败,则会执行@else语句。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*===== SCSS =====*/
$type: monster;
p {
@if $type==ocean {
color: blue;
} @else if $type==matador {
color: red;
} @else if $type==monster {
color: green;
} @else {
color: black;
}
}

/*===== CSS =====*/
p {
color: green;
}

@for

@for指令用于重复输出一组样式,每次重复时都会存在一个计数器变量用于调整输出结果。该指令拥有@for $var from <start> through <end>@for $var from <start> to <end>两种形式。

$var可以是任意变量名(例如$i),<start><end>是返回整数的 SassScript 表达式,如果<start>大于<end>,那么计数器将会进行递减而非递增。

@for指令会设置$var为指定范围内的连续数值,对于from...through数值范围包括<start><end>的值,对于from...to会从<start>开始运行,但不会包括<end>的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*===== SCSS =====*/
@for $i from 1 through 3 {
.item-#{$i} {
width: 2em * $i;
}
}

/*===== CSS =====*/
.item-1 {
width: 2em;
}

.item-2 {
width: 4em;
}

.item-3 {
width: 6em;
}

@each

@each指令的使用格式为@each $var in <list or map>,其中$var可以是任意变量名称(例如$length$name),而<list or map>是一个返回list或者map的 SassScript 表达式。

@each规则设置$varlistmap当中的每一项,输出样式中将会使用$var的实际值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*===== SCSS =====*/
@each $animal in puma, sea-slug, egret, salamander {
.#{$animal}-icon {
background-image: url("/images/#{$animal}.png");
}
}

/*===== CSS =====*/
.puma-icon {
background-image: url("/images/puma.png");
}

.sea-slug-icon {
background-image: url("/images/sea-slug.png");
}

.egret-icon {
background-image: url("/images/egret.png");
}

.salamander-icon {
background-image: url("/images/salamander.png");
}

多重赋值

@each指令能够以@each $var1, $var2, ... in <list>的格式使用多个变量,如果<list>是一个列表中的列表元素,那么子列表中的每个元素将会被分配至各自的变量,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*===== SCSS =====*/
@each $animal, $color, $cursor in (puma, black, default), (
sea-slug,
blue,
pointer
), (egret, white, move)
{
.#{$animal}-icon {
background-image: url("/images/#{$animal}.png");
border: 2px solid $color;
cursor: $cursor;
}
}

/*===== CSS =====*/
.puma-icon {
background-image: url("/images/puma.png");
border: 2px solid black;
cursor: default;
}

.sea-slug-icon {
background-image: url("/images/sea-slug.png");
border: 2px solid blue;
cursor: pointer;
}

.egret-icon {
background-image: url("/images/egret.png");
border: 2px solid white;
cursor: move;
}

由于maps被视为一个键值对的列表,所以多重赋值也能够正常的工作,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*===== SCSS =====*/
@each $header, $size in (h1: 2em, h2: 1.5em, h3: 1.2em) {
#{$header} {
font-size: $size;
}
}

/*===== CSS =====*/
h1 {
font-size: 2em;
}

h2 {
font-size: 1.5em;
}

h3 {
font-size: 1.2em;
}

@while

@while指令可以用来重复输出嵌套的样式,直至 SassScript 表达式返回结果为false,可以用于实现比@for语句更复杂的循环,但日常开发较少使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*===== SCSS =====*/
$i: 6;
@while $i>0 {
.item-#{$i} {
width: 2em * $i;
}
$i: $i - 2;
}

/*===== CSS =====*/
.item-6 {
width: 12em;
}

.item-4 {
width: 8em;
}

.item-2 {
width: 4em;
}

Mixin 指令

混入(Mixin)可以用来定义 Web 应用当中需要复用的样式,避免出现.float-left这样的无语义 class。混入里可以包含 CSS 规则以及 SASS 语法,甚至可以携带参数和引入变量,从而方便的生成各种灵活的样式。

定义混入:@mixin

混入(Mixin)通过 SASSR 提供的@mixin指令来定义,后面跟随混入的名称与参数以及内容块,例如下面代码定义了一个名为large-text的 Mixin:

1
2
3
4
5
6
7
8
@mixin large-text {
font: {
family: Arial;
size: 20px;
weight: bold;
}
color: #ff0000;
}

混入 Mixin 当中可以同时包含选择器和 CSS 属性,其中选择器还可以包含父级的选择器,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
@mixin clearfix {
display: inline-block;
&:after {
content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
* html & {
height: 1px;
}
}

因为历史原因,混入 Mixin 的名字以及其它 SASS 标识符可以互换连字符-和下划线_,例如对于名称为add-column的混入,也可以将其视为add_column,反之亦然。

包含混入:@include

@include指令通过混入的名称和可选的参数,可以引入特定的混入 Mixin 至文档,从而包含并复用其样式,例如下面代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*===== SCSS =====*/
@mixin large-text {
font: {
family: Arial;
size: 20px;
weight: bold;
}
color: #ff0000;
}

.page-title {
@include large-text;
padding: 4px;
margin-top: 10px;
}

/*===== CSS =====*/
.page-title {
font-family: Arial;
font-size: 20px;
font-weight: bold;
color: #ff0000;
padding: 4px;
margin-top: 10px;
}

混入 Mixin 可以包含在其它规则之外,只要它们不直接定义 CSS 属性或使用父选择器引用,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*===== SCSS =====*/
@mixin silly-links {
a {
color: blue;
background-color: red;
}
}

@include silly-links;

/*===== CSS =====*/
a {
color: blue;
background-color: red;
}

混入 Mixin 也可以包含其它的混入,接下来看下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
@mixin compound {
@include highlighted-background;
@include header-text;
}

@mixin highlighted-background {
background-color: #fc0;
}

@mixin header-text {
font-size: 20px;
}

事实上,混入 Mixin 还可以包含自己,但是在 SASS 3.3 版本之前,这种递归调用的行为是被禁止的。另外,只有定义了后代选择器的混入 Mixin 可以安全的混入源文件的顶层。

参数

混入 Mixin 可以使用 SassScript 的值作为参数,参数被包括在混入当中并作为为变量提供给其内部使用。多个参数通过逗号,分隔,然后通过传递对应顺序的参数进行调用,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*===== SCSS =====*/
@mixin sexy-border($color, $width) {
border: {
color: $color;
width: $width;
style: dashed;
}
}

p {
@include sexy-border(blue, 1in);
}

/*===== CSS =====*/
p {
border-color: blue;
border-width: 1in;
border-style: dashed;
}

混入 Mixin 可以通过变量赋值语法为参数指定默认值,调用的时候如果缺省参数,则使用默认值代替,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*===== SCSS =====*/
// 定义参数$width的默认值为1
@mixin sexy-border($color, $width: 1in) {
border: {
color: $color;
width: $width;
style: dashed;
}
}

p {
@include sexy-border(blue);
}

h1 {
@include sexy-border(blue, 2in);
}

/*===== CSS =====*/
p {
border-color: blue;
border-width: 1in;
border-style: dashed;
}

h1 {
border-color: blue;
border-width: 2in;
border-style: dashed;
}

关键字参数

混入 Mixin 在通过@include指令进行引入时也可以使用显式的关键字参数,例如可以将上面例子改写为:

1
2
3
4
5
6
7
p {
@include sexy-border($color: blue);
}

h1 {
@include sexy-border($color: blue, $width: 2in);
}

虽然上面的方式不够简明,但能够使 Scss 代码更加容易阅读,并让函数接口更加灵活,并方便的进行多参数的混入。命名参数可以按照任意顺序进行传递,如果有默认值可以省略。并且由于命名参数本质上是变量名称,因此下划线_和连字符-可以互换使用。

尾部逗号

如果传入混入 Mixin或者函数 Function的最后一个参数是位置性的或关键字风格的,那么可以在这个参数后面跟随一个逗号,,这种编码风格可以更加简洁的进行重构,并且减少语法错误。

可变参数

有些场景下,混入 Mixin或者函数 Function的参数个数是不确定的,例如创建一个可以接收任意数量参数的box-shadow混入。SASS 里可以通过...符号添加可变参数支持,将所有剩余的参数包装到一个列表 List。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*===== SCSS =====*/
@mixin box-shadow($shadows...) {
-moz-box-shadow: $shadows;
-webkit-box-shadow: $shadows;
box-shadow: $shadows;
}

.shadows {
@include box-shadow(0px 4px 5px #666, 2px 6px 10px #999);
}

/*===== CSS =====*/
.shadows {
-moz-box-shadow: 0px 4px 5px #666, 2px 6px 10px #999;
-webkit-box-shadow: 0px 4px 5px #666, 2px 6px 10px #999;
box-shadow: 0px 4px 5px #666, 2px 6px 10px #999;
}

可变参数可以包含任意需要传递给混入 Mixin或者函数 Function的关键字参数,它们可以通过keywords($args)函数 进行访问,该函数将会返回一个 Map,将它们作为一个从字符串(不包含$)到值的映射返回。

可变参数也可以在调用 Mixin 的时候使用,使用相同的语法能够扩展一个值的列表 List,以便每个值作为单独的参数传入;或者扩展值的 Map,从而使每个键值对都会作为一个关键字参数来进行处理。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*===== SCSS =====*/
@mixin colors($text, $background, $border) {
color: $text;
background-color: $background;
border-color: $border;
}

// 使用List作为参数
$values: #ff0000, #00ff00, #0000ff;
.primary {
@include colors($values...);
}

// 使用Map作为参数
$value-map: (
text: #00ff00,
background: #0000ff,
border: #ff0000,
);
.secondary {
@include colors($value-map...);
}

/*===== CSS =====*/
.primary {
color: #ff0000;
background-color: #00ff00;
border-color: #0000ff;
}

.secondary {
color: #00ff00;
background-color: #0000ff;
border-color: #ff0000;
}

可以同时传递一个 List 和一个 Map 参数,只要 List 参数位于 Map 类型参数之前,例如@include colors($values..., $map...)

我们可以通过可变参数包装 Mixin 并添加额外样式,而不改变 Mixin 的参数签名。这样参数将会通过被包装的 Mixin 直接进行传递,例如:

1
2
3
4
5
6
7
8
9
@mixin wrapped-stylish-mixin($args...) {
font-weight: bold;
@include stylish-mixin($args...);
}

.stylish {
// $width参数将会以关键字的形式传递到stylish-mixin
@include wrapped-stylish-mixin(#00ff00, $width: 100px);
}

传入内容块到 Mixin

样式块可以传递到混入 Mixin 所包含样式的位置,它们会出现在 Mixin 内任意@content指令的位置,从而能够定义关联到选择器和指令的构造的抽象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*===== SCSS =====*/
@mixin apply-to-ie6-only {
* html {
@content;
}
}

@include apply-to-ie6-only {
#logo {
background-image: url(/logo.gif);
}
}

/*===== CSS =====*/
* html #logo {
background-image: url(/logo.gif);
}

同样的 Mixin 可以在简写语法的.sass文件中完成(@mixin@include分别用=+表示)。

1
2
3
4
5
6
7
=apply-to-ie6-only
* html
@content

+apply-to-ie6-only
#logo
background-image: url(/logo.gif)

注意: 当@content指令被指定多次或者位于一个循环当中,样式块将会在每次调用中被复制并引用。

有些 Mixin 可能需要传入一个内容块,或者根据是否传入内容块而具备不同的行为。当内容块传递至当前 Mixin 的时候,content-exists()函数将会返回true,从而该函数可以用于帮助实现这类行为。

变量作用域与内容块

传递给 Mixin 的内容块会在其定义的作用域中进行运算,而非混入 Mixin 的作用域。这意味 Mixin 当中的局部变量不能传递至样式块,并将其解析为全局值使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*===== SCSS =====*/
$color: white;
@mixin colors($color: blue) {
background-color: $color;
@content;
border-color: $color;
}

.colors {
@include colors {
color: $color;
}
}

/*===== CSS =====*/
.colors {
background-color: blue;
color: white;
border-color: blue;
}

此外,这还清楚的表明,在被传递的内容块中使用的变量与 Mixin,会关联到内容块定义位置的其它样式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*===== SCSS =====*/
@mixin smartphone {
@content;
color: gold;
}

#sidebar {
$sidebar-width: 300px;
width: $sidebar-width;
@include smartphone {
width: $sidebar-width / 3;
}
}

/*===== CSS =====*/
#sidebar {
width: 300px;
width: 100px;
color: gold;
}

函数指令

SASS 支持开发人员自定义函数,并且在任意值或脚本上下文里使用,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*===== SCSS =====*/
$grid-width: 40px;
$gutter-width: 10px;

@function grid-width($n) {
@return $n * $grid-width + ($n - 1) * $gutter-width;
}

#sidebar {
width: grid-width(5);
}

/*===== CSS =====*/
#sidebar {
width: 240px;
}

正如上面代码所展示的,函数可以访问任意全局变量并接收参数,其行为类似一个混入 Mixin。函数可以包含语句,并且必须调用@return来设置函数的返回值。

与 Mixin 一样,SASS 可以通过关键字参数调用自定义函数,因此可以像下面这样调用上面例子中的函数:

1
2
3
#sidebar {
width: grid-width($n: 5);
}

建议在函数前面添加前缀以避免命名冲突,同时也能够有效区分哪些是自定义的函数,例如为自定义函数添加公司名称作为前缀-uinika-grid-width。自定义函数同样也支持可变参数,使用方式与混入 Mixin 相同。

同样由于历史原因,函数名以及其它 SASS 标识符当中的连字符-和下划线_都是可以互换的,如果定义了一个名为grid-width的函数,那么同样可以通过grid_width进行调用,反之亦然。

输出类型

虽然 SASS 默认输出的 CSS 格式是良好的,但是可以根据开发人员的个人习惯,使用 SASS 提供的其它 4 种输出格式。即可以通过:style选项进行设定,也可以在命令行中直接使用--style标志。

:nested

嵌套 nested是 SASS 默认的输出格式,每个属性都会占据一行,但缩进不是固定的,每个规则都基于其嵌套深度进行缩进。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
#main {
color: #fff;
background-color: #000;
}
#main p {
width: 10em;
}

.huge {
font-size: 10em;
font-weight: bold;
text-decoration: underline;
}

嵌套格式在阅读较大 CSS 工程文件时能够帮助开发人员快速的了解代码的结构。

:expanded

扩展 expanded格式类似于人工手写的 CSS 样式,每个属性和规则都独占用一行。规则当中的 CSS 属性会进行缩进,但规则本身并不会进行任何特殊的缩进,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
#main {
color: #fff;
background-color: #000;
}
#main p {
width: 10em;
}

.huge {
font-size: 10em;
font-weight: bold;
text-decoration: underline;
}

:compact

紧凑 compact格式比起上述两种格式占据的空间更小,这种格式将重点聚焦在选择器而非 CSS 属性上。每个 CSS 规则独自占据一行,该行还包括全部的 CSS 属性。每个嵌套的规则都会另起新行,不嵌套的选择器会输出空白行作为分隔,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
#main {
color: #fff;
background-color: #000;
}
#main p {
width: 10em;
}

.huge {
font-size: 10em;
font-weight: bold;
text-decoration: underline;
}

:compressed

压缩 compressed格式会尽可能减小所生成文件的体积,并在该文件末尾产生一个换行。除了必要的选择器分隔之外,几乎没有多余的空格。这种格式会让颜色等 CSS 属性值以最简短的方式来进行表示,由于代码可读性极差,因此主要用于生产环境。

1
2
3
4
5
6
7
8
9
10
11
12
#main {
color: #fff;
background-color: #000;
}
#main p {
width: 10em;
}
.huge {
font-size: 10em;
font-weight: bold;
text-decoration: underline;
}

扩展 SASS

对于前端开发人员的特殊需求,SASS 为提供了多项高阶定制功能,但是使用这些功能需要非常熟悉Ruby。例如自定义 SASS 函数、缓存存储、自定义导入器等等,这些功能在日常前端开发工作当中并不常用,可以根据需要查阅 SASS 的源码 API 文档。

SCSS 3.5.5 简明上手指南

http://www.uinio.com/Web/Scss/

作者

Hank

发布于

2014-01-20

更新于

2017-11-29

许可协议