CSS预处理器 —— Sass
一、sass简介
1. 特色功能
- 完全兼容 CSS3
- 在 CSS 基础上增加 变量 (variables)、嵌套 (nested rules)、混合 (mixins)、导入 (inline imports) 等功能
- 通过函数进行颜色值与属性值的运算
- 提供**控制指令 (control directives)**等高级功能
- 自定义输出格式
2. 语法格式
Sass 有两种语法格式。
首先是 SCSS (Sassy CSS)
- 这种格式以
.scss
作为拓展名 - 这种格式仅在 CSS3 语法的基础上进行拓展,所有 CSS3 语法在 SCSS 中都是通用的,同时加入 Sass 的特色功能。
- SCSS 也支持大多数 CSS hacks 写法以及浏览器前缀写法 (vendor-specific syntax),以及早期的 IE 滤镜写法。
另一种也是最早的 Sass 语法格式
- 被称为缩进格式 (Indented Sass) ,通常简称 “Sass”,是一种简化格式。
- 这种格式以
.sass
作为拓展名 - 它使用 “缩进” 代替 “花括号” 表示属性属于某个选择器,用 “换行” 代替 “分号” 分隔属性,很多人认为这样做比 SCSS 更容易阅读,书写也更快速。
- 缩进格式也可以使用 Sass 的全部功能,只是与 SCSS 相比个别地方采取了不同的表达方式,具体请查看 the indented syntax reference。
任何一种格式可以直接 导入 (@import)
到另一种格式中使用,或者通过 sass-convert
命令行工具转换成另一种格式:
# 将 Sass 转换成 SCSS
$ sass-convert style.sass style.scss
# Convert SCSS to Sass
$ sass-convert style.scss style.sass
3. 安装
sass
基于Ruby
语言开发而成,window下安装 sass
首先需要 安装Ruby。(注: mac下自带Ruby无需在安装Ruby!)
3.1 Ruby安装
先从官网下载Ruby,安装过程中请注意勾选 Add Ruby executables to your PATH
添加到系统环境变量。如下图:
安装完成后需测试安装有没有成功,运行CMD
输入以下命令:
ruby -v
# 如安装成功会打印
ruby 2.6.4p104 (2019-08-28 revision 67798) [x64-mingw32]
3.2 Sass安装
Ruby
自带一个叫做 RubyGems
的系统,用来安装基于Ruby
的软件。我们可以使用这个系统来 轻松地安装Sass
和Compass
。要安装最新版本的Sass
和Compass
,你需要输入下面的命令:
# 安装如下(如mac安装遇到权限问题需加 sudo gem install sass)
gem install sass
gem install compass
安装完成之后,你应该通过运行下面的命令来确认应用已经正确地安装到了电脑中:
sass -v
# Sass 3.x.x (Selective Steve)
compass -v
# Compass 1.x.x (Polaris)
# ...
如下sass常用更新、查看版本、sass命令帮助等命令:
# 更新sass
gem update sass
# 查看sass版本
sass -v
# 查看sass帮助
sass -h
3.3 编译sass
sass
编译有很多种方式,如命令行编译模式、sublime插件SASS-Build
、编译软件koala
、前端自动化软件codekit
、Grunt打造前端自动化工作流grunt-sass
、Gulp打造前端自动化工作流gulp-ruby-sass
等。
3.3.1 命令行编译模式
Sass 命令行工具根据文件的拓展名判断所使用的语法格式,没有文件名时 sass
命令默认编译 .sass
文件,添加 --scss
选项或者使用 scss
命令编译 SCSS 文件
# 单文件转换命令 sass 源文件 目标文件
sass input.scss output.css
# 单文件监听命令 sass --watch 源文件:目标文件
sass --watch input.scss:output.css
# 监听整个文件夹
sass --watch app/sass:public/stylesheets
# 编译格式 --style表示解析后的css是什么排版格式
# sass内置有四种编译格式: nested | expanded | compact | compressed
sass --watch input.scss:output.css --style compact
# 编译添加调试map
# --sourcemap表示开启sourcemap调试, 开启sourcemap调试后,会生成一个后缀名为.css.map文件
sass --watch input.scss:output.css --sourcemap
# 选择编译格式并添加调试map
sass --watch input.scss:output.css --style expanded --sourcemap
# 开启debug信息
sass --watch input.scss:output.css --debug-info
sass的四种编译风格:
-
nested:嵌套缩进的css代码,它是默认值。
-
expanded:没有缩进的、扩展的css代码。
-
compact:简洁格式的css代码(一种选择器的规则在单独一行)。
-
compressed:压缩后的css代码。
3.3.2 软件方式编译
- 推荐国产免费图形编译工具 Koala
- VScode扩展 Live Sass
"liveSassCompile.settings.formats":[
// 扩展
{
//可定制的出口CSS样式(expanded,compact,compressed,nested)
"format": "compact",
"extensionName": ".min.css",//编译后缀名
"savePath": null//编译保存的路径
}
],
"liveSassCompile.settings.excludeList": [
"**/node_modules/**",
".vscode/**"
],
二、嵌套规则(css功能扩展)
1 后代选择器
Sass 允许将 一套 CSS 样式 嵌套进 另一套样式中,内层的样式将它外层的选择器 作为父选择器。嵌套功能 避免了重复输入父选择器,而且令复杂的 CSS 结构更易于管理。
在Sass中,你可以像俄罗斯套娃那样在规则块中嵌套规则块。sass
在输出css
时会帮你把这些嵌套规则处理好,避免你的重复书写:
#main {
color: #00ff00;
width: 97%;
.redbox {
background-color: #ff0000;
color: #000000;
}
}
编译结果(sass --watch demo.sass:demo.css --style compact)
#main {color: #00ff00; width: 97%; }
#main .redbox {background-color: #ff0000; color: #000000; }
过程:sass
用了两步,每一步都是像打开俄罗斯套娃那样把里边的嵌套规则块一个个打开。
- 首先,把
#main
(父级)这个id
放到.redbox
选择器(子级)的前边; - 然后,
#main .redbox
里边还有嵌套的规则,sass
重复一遍上边的步骤,把新的选择器添加到内嵌的选择器前边
大多数情况下这种简单的嵌套都没问题,但是有些场景下不行,比如你想要在嵌套的选择器 里边立刻应用一个类似于:hover
的伪类。为了解决这种以及其他情况,sass
提供了一个特殊结 构&
。
2 父选择器的标识符&
一般情况下,sass
在解开一个嵌套规则时就会把父选择器(#main
)通过一个空格连接到子选择器的前边(.redbox
)形成(#main .redbox
)。这种在CSS里边被称为后代选择器,因为它选择ID为 main
的元素内所有命中选择器.redbox
的元素。但在有些情况下你却不会希望sass
使用这种后代选择器的方式生成这种连接。
用法一:作为普通的父选择器
最常见的一种情况是当你为链接之类的元素写:hover
这种伪类时,你并不希望以后代选择器的方式连接。比如说,下面这种情况sass
就无法正常工作:
article a {
color: blue;
:hover { color: red }
}
这意味着color: red
这条规则将会被应用到选择器article a :hover
,article
元素内链接的所有子元素在被hover
时都会变成红色。这与我们的初衷是不符合的。
解决之道为使用一个特殊的sass
选择器,即父选择器。在使用嵌套规则时,父选择器能对于嵌套规则如何解开提供更好的控制。它就是一个简单的&
符号,且可以放在任何一个选择器可出现的地方 。
article a {
color: blue;
&:hover { color: red }
}
当包含父选择器标识符的嵌套规则被打开时,它不会像后代选择器那样进行拼接,而是&
被父选择器直接替换:
article a { color: blue }
article a:hover { color: red }
用法二:在父选择器之前添加选择器
比如,在js
代码中根据浏览器判断,是IE
时就给body
标签添加一个 .ie
选择器,为这种情况编写特殊的样式如下:
#content aside {
color: red;
body.ie & { color: green } // 在父选择器之前添加选择器 body.ie
}
// 编译后
#content aside {color: red};
body.ie #content aside { color: green }
用法三:用作选择器名称的占位符
&
必须作为选择器的第一个字符,其后可以跟随后缀生成复合的选择器,例如
#main {
color: black;
&-sidebar { border: 1px solid; } // 用作占位符,编译时会用父选择器的名称替换&
}
// 编译后
#main {color: black; }
#main-sidebar {border: 1px solid; }
注意:当父选择器含有不合适的后缀时,Sass 将会报错。
3 群组选择器的嵌套
在CSS
里边,选择器 button
会同时命中所有的 button
元素。这种选择器称为群组选择器。群组选择器 的规则会对命中群组中任何一个选择器的元素生效。
.container {
h1, h2, h3 {
a {margin-bottom: .8em}
}
}
当sass
解开一个群组选择器规则内嵌的规则时,它会把每一个内嵌选择器的规则都正确地解出来:
// 编译后
.container h1 a, .container h2 a, .container h3 a { margin-bottom: .8em }
过程:首先sass
将.container
和h1
、.container
和h2
、.container
和h3
分别组合,然后将三者重新组合成一个群组选择器。然后对于内嵌在群组选择器内的嵌 套规则,处理方式也一样,将.container h1
和a
、.container h2
和a
、.container h3
和a
再分别组合,然后重新组合成一个群组选择器,生成上面的普通css样式。
缺点:虽然sass
让你的样式表看上去很小,但实际生成的css
却可能非常大,这会降低网站的速度。
4 子组合选择器 >
和同层组合选择器:+
和 ~
这三个组合选择器 必须和其他选择器配合使用,以指定浏览器仅选择某种特定上下文中的元素。
article {
> section { background: #eee } // 直接子元素 >
nav + & { margin-top: 0 } // 父选择器之前添加选择器
~ article { border-top: 1px dashed #ccc } // 同层组合选择器 ~
dl > {
dt { color: #333 }
dd { color: #555 }
}
}
编译结果
// 子组合选择器> : 选择article的直接子元素footer
article > footer { background: #eee }
// 同层组合选择器~ : 选择nav后紧跟的article元素
nav + article { margin-top: 0 }
// 同层全体组合选择器~ : 选择所有跟在article后的同层article元素,不管它们之间隔了多少其他元素
article ~ article { border-top: 1px dashed #ccc }
article dl > dt { color: #333 }
article dl > dd { color: #555 }
5 嵌套属性
有些 CSS 属性遵循相同的命名空间 (namespace),比如 font-family, font-size, font-weight
都以 font
作为属性的命名空间。为了便于管理这样的属性,同时也为了避免了重复输入,Sass 允许将属性嵌套在命名空间中
嵌套属性的规则是这样的:把属性名从中划线
-
的地方断开,在根属性后边添加一个冒号:
,紧跟一个{ }
块,把子属性部分写在这个{ }
块中。
就像css
选择器嵌套一样,sass
会把你的子属性一一解开,把根属性和子属性部分通过中划线-连接起来,最后生成的效果与你手动一遍遍写的css
样式一样
nav {
border: {
style: solid;
width: 1px;
color: #ccc;
}
}
// 编译后
nav {
border-style: solid;
border-width: 1px;
border-color: #ccc;
}
命名空间也可以包含自己的属性值,例如:
.funky {
font: 20px/24px {
family: 'Microsoft Yahei';
weight: bold;
}
}
// 编译后
.funky {
font: 20px/24px;
font-family: fantasy;
font-weight: bold;
}
对于属性的缩写形式,你甚至可以像下边这样来嵌套,指明例外规则:
nav {
border: 1px solid #ccc {
left: 0px;
right: 0px;
}
}
// 编译后
nav {
border: 1px solid #ccc;
border-left: 0px;
border-right: 0px;
}
6 占位符选择器 %foo
Sass 额外提供了一种特殊类型的选择器:占位符选择器 (placeholder selector)。与常用的 id 与 class 选择器写法相似,只是 #
或 .
替换成了 %
。必须通过 @extend 指令调用,更多介绍请查阅 @extend-Only Selectors。
当占位符选择器单独使用时(未通过 @extend
调用),不会编译到 CSS 文件中。
三、 注释
Sass 支持标准的 CSS 多行注释 /* */
,以及单行注释 //
,前者会 被完整输出到编译后的 CSS 文件中,而后者则不会
将 !
作为多行注释的第一个字符表示在压缩输出模式下保留这条注释并输出到 CSS 文件中,通常用于添加版权信息。
插值语句 (interpolation) 也可写进多行注释中输出变量值:
$version: "1.2.3";
/* This CSS is generated by My Snazzy Framework version #{$version}. */
// 编译为
/* This CSS is generated by My Snazzy Framework version 1.2.3. */
四、sassScript
在 CSS 属性的基础上 Sass 提供了一些名为 SassScript 的新功能。 SassScript 可作用于任何属性,允许属性使用变量、算数运算等额外功能。
通过 interpolation,SassScript 甚至可以生成选择器或属性名,这一点对编写 mixin 有很大帮助。
4.1 Interactive Shell
Interactive Shell 可以在命令行中测试 SassScript 的功能。在命令行中输入 sass -i
,然后输入想要测试的 SassScript 查看输出结果:
$ sass -i
>> "Hello, Sassy World!"
"Hello, Sassy World!"
>> 1px + 1px + 1px
3px
>> #777 + #777
#eeeeee
>> #777 + #888
white
4.2 变量$
sass
使用$
符号来标识变量(老版本的sass
使用!
来标识变量。改成KaTeX parse error: Expected 'EOF', got '是' at position 1: 是̲多半因为`!highlight…highlight-color和
KaTeX parse error: Expected 'EOF', got '。' at position 15: sidebar-width`。̲为什么选择`符号呢?因为它好认、更具美感,且在CSS中并无他用,不会导致与现存或未来的
css`语法冲突。
1. 变量命名
sass
的变量名可以与css
中的属性名和选择器名称相同,包括中划线和下划线(使用中划线的方式更为普遍)。这两种用法fu相互兼容。用中划线声明的变量可以使用下划线的方式引用,反之亦然。这意味着即使compass
选择用中划线的命名方式,这并不影响你在使用compass
的样式中用下划线的命名方式进行引用:
$link-color: blue; // 中划线方式命名
a { color: $link_color; } // 可以通过 下划线 方式引用
//编译后
a { color: blue; }
注意:在sass
中纯css
部分不互通,比如类名、ID或属性名。
2. 变量声明
变量以美元符号开头,赋值方法与 CSS 属性的写法一样;
$highlight-color: #F90;
// 以空格分割的多个属性值
$basic-border: 1px solid black;
// 以逗号分割的多个属性值
$plain-font: Myriad,Helvetica,"Liberation Sans",Arial,sans-serif;
- 任何可以用作
css
属性值的赋值都可以用作sass
的变量值。 - 变量声明后还未生效,只有当变量被引用后才生效
- 在声明变量时,变量值也可以引用其他变量。
$highlight-color: #F90;
$highlight-border: 1px solid $highlight-color; // 声明变量时,变量值也可以引用其他变量
.selected {
border: $highlight-border; // 当变量被引用后才生效
}
//编译后
.selected {border: 1px solid #F90;}
3. 变量引用
1. 凡是`css`属性的标准值可存在的地方,变量就可以使用。`css`生成时,变量会被它们的值所替代。
2. 变量可以修改,但一经修改,**所有**引用此变量的地方(**同一作用域**)生成的值**都会随之改变**
3. 如果变量要**镶嵌在字符串之中**,就必须需要写在 **`#{}`**之中,也就是后面要讲到的 **插值语句**
$side : top-left;
.rounded { border-#{$side}-radius: 5px; }
// 编译后
.rounded { border-top-left-radius: 5px; }
4. 变量作用域
1. 全局作用域:不在嵌套规则内定义的变量则可在任何地方使用(嵌套规则外 -- 全局变量)
2. 块级作用域:嵌套规则内定义的变量只能在嵌套规则内使用(嵌套规则内 -- 局部变量)
3. 将局部变量转换为全局变量可以添加 `!global` 声明(嵌套规则内 -- 全局变量 -- $变量名: 变量值 !global;)
#main {
$width: 5em !global; // 加了!global则$width变成了全局变量
width: $width;
}
#sidebar {
width: $width;
}
4.3 数据类型
SassScript 支持 6 种主要的数据类型(变量的6中主要数据类型):
- 数字,
1, 2, 13, 10px
- 字符串,有引号字符串与无引号字符串,
"foo", 'bar', baz
- 颜色,
blue, #04a3f9, rgba(255,0,0,0.5)
- 布尔型,
true, false
- 空值,
null
- 数组 (list),用空格或逗号作分隔符,
1.5em 1em 0 2em, Helvetica, Arial, sans-serif
- maps, 相当于 JavaScript 的 object,
(key1: value1, key2: value2)
SassScript 也支持其他 CSS 属性值,比如 Unicode 字符集,或 !important
声明。然而Sass 不会特殊对待这些属性值,一律视为无引号字符串
1. 字符串
- 有引号字符串 (quoted strings) :如
"Lucida Grande"
'Microsoft Yahei'
; - 无引号字符串 (unquoted strings),如
sans-serif
、bold
,在编译 CSS 文件时不会改变其类型
$font-family: 'Microsoft Yahei', sans-serif; // 一个有引号字符串,一个无引号字符串
body {
// 嵌套属性的写法
font: 14px/24px {
family: $font-family;
};
}
// 编译后. 可发现在编译css文件时不会改变字符串的类型
body { font: 14px/24px; font-family: "Microsoft Yahei", sans-serif; }
- 例外情况:使用
#{}
(插值) 时,有引号字符串将被编译为无引号字符串,这样便于在 mixin 中引用选择器名:
@mixin firefox-message($selector) {
body.firefox #{$selector}:before {//会将传入的有引号字符串".header"编译成了无引号字符串
content: "Hi, Firefox users!";
}
}
@include firefox-message(".header");
// 编译后
body.firefox .header:before {content: "Hi, Firefox users!"; }
2. 颜色
任何CSS颜色表达式都返回一个SassScript颜色值,这包括大量与未引用字符串无法区分的命名颜色。在压缩输出模式下,Sass将输出颜色的最小CSS表示。例如,#FF0000在压缩模式下将输出为red,而blanchedalmond将输出为#FFEBCD。
$color: #ff0000;
body {
color: $color;
p {color: #ff0000;}
}
// 编译后 sass --watch demo.scss:demo.css --style compressed
body { color:red; }
body p { color: #ff0000; }
用户在使用命名颜色时遇到的一个常见问题是,由于Sass首选的输出格式与在其他输出模式中键入的格式相同,因此在压缩时插入选择器的颜色将成为无效语法。为了避免这种情况,如果要在选择器的构造中使用命名颜色,请始终引用它们。
3. 数组 (Lists)
数组 (lists) 指 Sass 如何处理 CSS 中 margin: 10px 15px 0 0
或者 font-face: Helvetica, Arial, sans-serif
这样通过 空格 或者 逗号 分隔的一系列的值。事实上,独立的值也被视为数组 —— 只包含一个值的数组。
数组本身没有太多功能,但 Sass list functions 赋予了数组更多新功能:
length()
获取数组长度index($list, $value)
返回一个值在列表中的位置值nth($list, $index)
函数可以直接访问数组中的某一项;join($list1, $list2, [$separator])
函数可以将多个数组连接在一起;append($list, $val, [$separator])
函数可以在数组中添加新值;zip($lists…)
将几个列表结合成一个多维的列表。。@each
指令能够遍历数组中的每一项。
示例:实现下面这种效果。HTML 结构很简单,就是一个 ul ,下面 6 个li,每个 li 的背景色不一样。
用传统CSS来写:
.ksxz_ul li:nth-child(1){
background:#f5ad1b;
}
.ksxz_ul li:nth-child(1):hover{
background:#f7bf4c;
}
// 。。。
以下代码是在 SCSS 中完成:
// 定义数组,数组元素用逗号隔开
$liColor: #f5ad1b,#5f89ce,#94bf45,#da8ec5,#78bfc2,#bec278;
// 开始 @each 循环遍历数组
// $c 作为循环变量,代表了数组的元素,不是索引~!!!
@each $c in $liColor{
$i:index($liColor, $c); // 获取 $c 在数组中的索引,并赋值给 $i 赋值用冒号,不是等号~!
li:nth-child( #{$i} ){ // 经典的地方来了,SCSS 循环是从 1 开始,不是 0 哦~
background: $c; // 背景色
&:hover{
background: lighten($c, 10%); // hover 后的颜色
}
}
}
数组中可以包含子数组
- 比如
1px 2px, 5px 6px
是包含1px 2px
与5px 6px
两个数组的数组。 - 如果内外两层数组使用相同的分隔方式,需要用圆括号包裹内层,所以也可以写成
(1px 2px) (5px 6px)
。 - 区别就是,之前的
1px 2px, 5px 6px
使用逗号分割了两个子数组 (comma-separated),而(1px 2px) (5px 6px)
则使用空格分割(space-separated)。
当数组被编译为 CSS 时,Sass 不会添加任何圆括号(CSS 中没有这种写法)
- 用
()
表示不包含任何值的空数组。空数组不可以直接编译成 CSS,比如编译font-family: ()
Sass 将会报错。 - 如果数组中包含空数组或空值,编译时将被清除,比如
1px 2px () 3px
或1px 2px null 3px
。
p { font-family: (); } // 编译将会报错 Invalid CSS after "...font-family: ()"
$border: 1px 2px null 3px; // 或者 $border: 1px 2px () 3px;
.rounded {
border: $border ;
}
// 编译后
.rounded { border: 1px 2px 3px; }
基于逗号分隔的数组允许保留结尾的逗号,这样做的意义是强调数组的结构关系,尤其是需要声明只包含单个值的数组时。例如 (1,)
表示只包含 1
的数组,而 (1 2 3,)
表示包含 1 2 3
这个以空格分隔的数组的数组。
4. Maps
Maps可视为键值对的集合,键被用于定位值。
- 和Lists不同,Maps必须被圆括号包围,键值对被逗号分割 。 Maps中的keys和values可以是sassscript的任何对象。
- 和Lists一样Maps主要为sassscript函数服务,如 map-get函数用于查找键值,map-merge函数用于map和新加的键值融合,@each命令可添加样式到一个map中的每个键值对。
- Maps可用于任何Lists可用的地方,在List函数中 Map会被自动转换为List , 如 (key1: value1, key2: value2)会被List函数转换为 key1 value1, key2 value2 ,反之则不能。
1. map-merge合并函数
2. map-get获取函数
//创建Maps类型的变量: Maps变量的值必须由圆括号包围,键值对以逗号分隔
$testMaps1: ( size: 14px, color: pink );
$testMaps2: (
color: #666,
family: (Arial, Helvetica),
size: 16px,
line-height: 1.4
);
// map-merge函数,将两个maps变量进行合并,生成一个新的maps变量
$testMaps: map-merge($testMaps1, $testMaps2);
body{
// 通过map-get函数查找某个key的value。可简写成 font-size: map-get($testMaps, size)
// 注意,size/line-height要使用插值语句,若是直接写/将会被认作是除号,从而进行除法运算
font: #{map-get($map: $testMaps, $key: size)}/#{map-get($testMaps, line-height)} {
family: map-get($testMaps, family)
}
color: map-get($testMaps, color);
}
// 编译后
body { font: 16px; font-family: Arial, Helvetica; line-height: 1.4; color: #666; }
3. map-has-key判断函数
// Map with much breakpoints
$breakpoints: (
small: 320px,
medium: 600px,
large: 768px
);
// Respond-To Mixin
@mixin respond-to($breakpoint) {
// map-has-key函数用于检测某个key是否存在
@if map-has-key($breakpoints, $breakpoint) {
$value: map-get($breakpoints, $breakpoint);
@media screen and (min-width: $value) {
@content;
}
}
@warning "Unknown `#{$breakpoint}` in $breakpoints";
}
// 使用
.m-tabs {
background-color: #f2f2f2;
@include reponse-to(medium) {
background-color: #666;
}
}
// 编译后
.m-tabs {
background-color: #f2f2f2;
}
@media screen and (min-width: 600px) {
background-color: #666;
}
4. @each 循环Maps变量
// 创建Maps类型的变量: Maps变量的值必须由圆括号包围,键值对以逗号分隔
$icons: (
checkmark: a,
plus: b,
minus: c
);
// @each $name, $value in 变量名 遍历maps变量 $icon,输出icons的所有类
@each $name, $value in $icons {
.icon--#{$name} {
content: $value;
}
}
// 编译结果
.icon--checkmark { content: a; }
.icon--plus { content: b; }
.icon--minus { content: c; }
5. nth函数
// _m-buttons.scss
$buttons: (
error: (#d82d2d, #666), // 值是一个数组:(背景颜色, 字体颜色)
success: (#52bf4a, #fff),
warning: (#c23435, #fff)
);
.m-button {
display: inling-block;
padding: .5em;
background: #ccc;
color: #666;
@each $name, $colors in $buttons {
$bgcolor: nth($colors, 1); // 通过nth获取数组中的第一项
$fontcolor: nth($colors, 2);// 通过nth获取数组中的第二项
&--#{$name} {
background-color: $bgcolor;
color: $fontcolor;
}
}
}
// 编译后
.m-button { display: inling-block; padding: .5em; background: #ccc; color: #666; }
.m-button--error { background-color: #d82d2d; color: #666; }
.m-button--success { background-color: #52bf4a; color: #fff; }
.m-button--warning { background-color: #c23435; color: #fff; }
6. 搭配函数使用
// _config.scss
$layer: (
offcanvas: 1,
lightbox: 500,
dropdown: 10,
tooltip: 15
);
// _m-lightboxes.scss
@function layer($name) {
@if map-has-key($layer, $name) {
@return map-get($layer, $name);
}
@warn "The key #{$name} is not in the map '$layer'";
@return null;
};
.m-lightbox {
z-index: layer(lightbox);
}
// 编译后
.m-lightbox { z-index: 500; }
// Scheme of colors
$colorscheme: (
gray: (
base: #ccc,
light: #f2f2f2,
dark: #666
),
brown: (
base: #ab906b,
light: #ecdac3,
dark: #5e421c
)
);
// 颜色获取函数
// 第一个参数是Sass map的对象($scheme) -- 在这个例子中可能是gray或者brown
// 第二个参数就是你想要的颜色($tone),默认值是base
@function setcolor($scheme, $tone: base) {
@return map-get(map-get($colorscheme, $scheme), $tone);
}
// 使用
.element {
color: setcolor(brown);
}
.element--light {
color: setcolor(brown, light);
}
// 编译后
.element { color: #ab906b; }
.element--light { color: #ecdac3; }
7. 高级:通过Classes定制主题
在项目中会经常需要通过一些基础代码创建多套主题,所以这里给出一个建议:在文档的最开始就定义一个 主题类 来满足特定的工作。我们需要一个对象以便能够处理不同名字的主题,同时给出不同的样式模块。
// _config.scss
$themes: (
theme1: theme-light,
theme2: theme-dark
);
$config: (
theme1: (
background: #f2f2f2,
color: #000
),
theme2: (
background: #666,
color: #fff
)
);
// 快速获取模块值的方法
// _functions.scss
@function setStyle($map, $theme, $style) {
@if map-has-key($map, $theme) {
@return map-get(map-get($map, $theme), $style);
}
@warn "The key `#{$object}` is not available in the map.";
@return null;
}
// 遍历主题
// _m-buttons.scss
.m-button {
// 遍历$themes拿到$key和$value,为不同主题创建map
@each $key, $value in $themes {
@if map-has-key($config, $key) {
.#{$value} & {
background: setStyle($config, $key, background);
color: setStyle($config, $key, color);
}
} @else {
@warn "The key `#{$key} isn't defined in the map $config`"
}
}
}
// 编译后
.theme-light .m-button { background: #f2f2f2; color: #000; }
.theme-dark .m-button { background: #666; color: #fff; }
4.5 运算
所有数据类型均支持相等运算 ==
或 !=
,此外,每种数据类型也有其各自支持的运算方式
1. 数字运算
SassScript 支持数字的运算有:
- 数字的加减乘除、取整等运算 (
+, -, *, /, %
),如果必要会在不同单位间转换值。 - 关系运算
<, >, <=, >=
- 可用于所有数据类型的相等运算 **
==, !=
**
$min-width: 200px;
$max-width: 600px;
// 加减乘除
.border-box {
width: ($max-width / 2) * 3 + $min-width - 10px; // .border-box { width: 1090px; }
}
数字内置函数:
percentage($number)
将一个不带单位的数值转成百分比round($number)
将$number
四舍五入为整数,$number
可带单位ceil($number)
大于$number
,向上取整floor($number)
与ceil()
相反,去除$number
小数,向下取整abs($number)
,返回$number
的绝对值min($numbers…)
,返回$number...
的最小值max($numbers…)
,返回$number...
的最大值random([$limit])
,返回一个随机数
1.1 除法运算
/
在 CSS 中通常起到分隔数字的用途,SassScript 作为 CSS 语言的拓展当然也支持这个功能,但是同时也赋予了 /
除法运算的功能。也就是说,如果 /
在 SassScript 中把两个数字分隔,编译后的 CSS 文件中也是同样的作用。 —— 在SassScript中, /
有两个作用:分隔数字、除法运算符
以下三种情况 /
将被视为除法运算符号:
- 如果值,或值的一部分,是变量或者函数的返回值
- 如果值被圆括号包裹
- 如果值是算数表达式的一部分
p {
$width: 1000px;
width: $width/2; // 值的一部分是变量 —— 除法运算符
width: round(1.5)/2; // 值是算术表达式的一部分 —— 除法运算符
height: (500px/2); // 值被圆括号包裹 —— 除法运算符
margin-left: 5px + 8px/2px; // 加减乘除运算
}
如果需要使用变量,同时又要确保 /
不做除法运算而是完整地编译到 CSS 文件中,只需要用 #{}
插值语句将变量包裹。
p {
$font-size: 12px;
$line-height: 30px;
font: #{$font-size}/#{$line-height};
}
// 编译结果
p { font: 12px/30px; }
2. 颜色值运算
众所周知,颜色是由RGB颜色构成的,也就是红绿蓝。表示颜色的数据也是规则的分段,由红绿蓝组成,所以颜色值的运算也是分段计算进行的,也就是分别计算红色,绿色,以及蓝色的值:
p {
color: #010203 + #040506;
}
// 计算 01 + 04 = 05、02 + 05 = 07、03 + 06 = 09,然后编译为
p { color: #050709; }
数字与颜色值之间也可以进行算数运算,同样也是分段计算的,比如
p {
color: #010203 * 2;
}
// 计算 01 * 2 = 02、02 * 2 = 04、03 * 2 = 06,然后编译为
p { color: #020406; }
需要注意的是,如果颜色值包含 alpha channel(rgba 或 hsla 两种颜色值),必须拥有相等的 alpha 值才能进行运算,因为算术运算不会作用于 alpha 值。
p {
color: rgba(255, 0, 0, 0.75) + rgba(0, 255, 0, 0.75);
}
// 编译为
p { color: rgba(255, 255, 0, 0.75); }
颜色值的 alpha channel 可以通过 opacify 或 transparentize 两个函数进行调整。
$trans-red: rgba(255, 0, 0, 0.5);
p {
color: opacify($trans-red, 0.3); // 使用 opacity 函数
background-color: transparentize($trans-red, 0.25); // 使用 transparentize 函数
}
// 编译为
p {
color: rgba(255, 0, 0, 0.8);
background-color: rgba(255, 0, 0, 0.25);
}
IE 滤镜要求所有的颜色值包含 alpha 层,而且格式必须固定 #AABBCCDD
,使用 ie_hex_str
函数可以很容易地将颜色转化为 IE 滤镜要求的格式。
$trans-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($trans-red)}');
}
// 编译为
div {
filter: progid:DXImageTransform.Microsoft.gradient(enabled='false', startColorstr=#FF00FF00, endColorstr=#80FF0000);
}
对于颜色的操作,scss提供了大量内置函数,非常方便。
3. 字符串运算
+
可用于连接字符串。有引号字符串+无引号字符串=有引号字符串;无引号字符串+有引号字符串=无引号字符串- 运算表达式与其他值连用时,用空格做连接符
- 在有引号的文本字符串中使用
#{}
插值语句可以添加动态的值 - 空的值被视作插入了空字符串
p {
cursor: e + -resize; // 使用+拼接字符串
margin: 3px + 4px auto; // 运算表达式与其他值连接时,用空格作为连接符
&::before{
content: "Foo " + Bar; // 有引号字符串+无引号字符串=有引号字符串
font-family: sans- + "serif"; //无引号字符串+有引号字符串=无引号字符串
}
&::after{
content: "I ate #{5 + 10} pies!"; // 在有引号的文本字符串中使用插值语句添加动态的值
}
a{
$value: null;
content: "I ate #{$value} pies!"; // 空的值被视作插入了空字符串
}
}
// 编译结果
p { cursor: e-resize; margin: 7px auto; }
p::before { content: "Foo Bar"; font-family: sans-serif; }
p::after { content: "I ate 15 pies!"; }
p a { content: "I ate pies!"; }
字符串内置函数
unquote($string)
删除$string
前后的引号。quote($string)
给$string
前后添加引号str-length($string)
返回$string
的长度str-insert($string, $insert, $index)
在指定位置插入字符str-index($string, $substring)
返回指定字符在字符串的位置to-upper-case($string)
将$string
小写字母转成大写字母to-lower-case($string)
将$string
大写字母转成小写字母
4. 布尔运算
SassScript 支持布尔型的 and
or
以及 not
运算。
5. 数组运算
数组不支持任何运算方式,只能使用 list functions 控制(见数据类型——数组一章)。
4.6 圆括号
圆括号可以用来影响运算的顺序:
p {
width: 1em + (2em * 3);
}
// 编译为
p { width: 7em; }
4.7 函数
SassScript 定义了多种函数,有些甚至可以通过普通的 CSS 语句调用:
p {
color: hsl(0, 100%, 50%); // hsla($hue, $saturation, $lightness, $alpha: 1)
border-width: if(true, 10px, 15px);
}
// p { color: red; border-width: 10px; }
Sass 函数允许使用关键词参数 (keyword arguments),上面的例子也可以写成:
p {
color: hsl($hue: 0, $saturation: 100%, $lightness: 50%);
}
通过 Sass::Script::Functions 查看完整的 Sass 函数列表,参数名,以及如何自定义函数。
全局函数
// hsl函数--返回值: color
hsl($hue $saturation $lightness)
hsl($hue $saturation $lightness / $alpha)
hsl($hue, $saturation, $lightness, $alpha: 1)
hsla($hue $saturation $lightness)
hsla($hue $saturation $lightness / $alpha)
hsla($hue, $saturation, $lightness, $alpha: 1)
// rgb函数--返回值: color
rgb($red $green $blue)
rgb($red $green $blue / $alpha)
rgb($red, $green, $blue, $alpha: 1)
rgb($color, $alpha)
rgba($red $green $blue)
rgba($red $green $blue / $alpha)
rgba($red, $green, $blue, $alpha: 1)
rgba($color, $alpha)
// 条件函数,类似于三元表达式
if($condition, $if-true, $if-false)
4.8 插值语句 #{} (Interpolation)
插值 (interpolation) —— 将一个占位符,替换成一个值。插值其实就是字符串连接的语法糖而已。
- 通过
#{}
插值语句可以在选择器或属性名中使用变量 #{}
插值语句也可以在属性值中插入 SassScript —— 可以避免 Sass 运行运算表达式,直接编译 CSS。
$name: foo;
$attr: border;
p.#{$name} { // 在选择器中使用变量
#{$attr}-color: blue; // 在属性名中使用变量
}
// p.foo { border-color: blue; }
p {
$font-size: 12px;
$line-height: 30px;
font: #{$font-size}/#{$line-height}; // 避免将/用作除法运算符
}
// p { font: 12px/30px; }
$colors: (
"primary": tomato,
"secondary": hotpink
);
// _function.scss
@function findColor($key) {
@if map-has-key($map: $colors, $key: $key) {
@return map-get($colors, $key);
}
@warn 'Key `#{$key}` is not found in $colors'; // 插值语句使用场景:打印字符串中变量的内容
@return #000;
}
.el {
background-color: findColor(primary2);
}
1. 与css函数
假设你想基于侧边栏的宽度设置主容器的大小。你是一个勤奋的前端开发者,已经把这个宽度存储在一个变量中,所以,你可能会这样做:
$sidebar-width: 250px;
.main {
width: calc(100% - $sidebar-width); //将变量用于css函数--初衷是想像css那样进行运算,但是。。
}
// 编译结果: .main { width: calc(100% - $sidebar-width); }
然后,你会惊讶地发现,根本不work。没有报错,容器的大小却又不正确。如果你去审查你的dom元素,你会看到这个被划掉了。因为,这是不合法的。
现在我们应该想到:calc()
是一个CSS函数,不是一个Sass函数。这就是说Sass会将整个表达式解释成一个字符串。你可以试试:
$sidebar-width: 250px;
.main {
$type-of-expression: type-of(calc(100% - $sidebar-width)); // 使用type-of判断类型
content: $type-of-expression;
}
// .main { content: string; }
因为这是一个字符串,难怪Sass表现和之前@warn
中的$colors
字符串一样。$sidebar-width
被认为是一个常规字符串,所以打出来就是它自己。但这都不是我们所想要的,是吧?我们用插值这样做。
.main {
width: calc(100% - #{$sidebar-width}); // 在编译时,会用250px替换#{$sidebar-width}
}
// 编译后:.main { width: calc(100% - 250px); }
我们仅仅在这里谈了calc()
,但其实和其他CSS 原生函数是一样的,包括伪类。比如:url()
,linear-gradient()
,radial-gradient()
,cubic-bezier()
。
以下是另一个使用CSS函数的例子:
@for $i from 1 through $max {
.el:nth-of-type(#{$i}) {
// ...
}
}
小结:Sass会把CSS函数认为是字符串,所以想要在最后获得它们的值,要求你转义所有同它们一起使用的变量。
2. css指令
我们将视转移到另一个有趣的变量插值场景:CSS指令,比如@support
,@page
,最重要的还是@media
。
现在,Sass是怎样解析CSS指令,尤其是demia指令的。我已经查看过Sass的源码,当我Ruby环境有点问题的时候,我找到一些有趣的事情。
def query_expr
interp = interpolation
return interp if interp
return unless tok(/\(/)
res = ['(']
ss
res << sass_script(:parse)
if tok(/:/)
res << ': '
ss
res << sass_script(:parse)
end
res << tok!(/\)/)
ss
res
end
第一行告诉Sass,如果有一个插值表达式的话,便返回media query
。如果找到一个开括号((
),便会一直往下走,解析所有的东西,反之就会抛出一个错误。我们在这里试试一个例子:
$value: screen;
@media $value {
// ...
}
毫不惊讶的是,这失败了:
Invalid CSS after "@media ": expected media query (e.g. print, screen, print and screen), was “$value {”
就像错误信息提示的那样,它期待一个media query。在这里,如果你的变量在 @media
字符串后面,需要使用插值才可以。比如:
$value: screen;
@media #{$value} {
// ...
}
和我们之前讨论的Ruby转义规则一样,如果@media
后面紧跟(()
),你就不再需要插值变量了,因为Sass会求出所有在这些括号里面的值。比如:
$value: 1336px;
@media (max-width: $value) {
// ...
}
在这个示例中,Sass将这个表达式(max-width: $value)
转化成(max-width: 1337px)
,最后生成合法的CSS结果。所以,我们便没有必要再对变量转义了。
4.9 & in SassScript
就像在选择器中使用一样,在SassScript
中 &
表示当前的父选择器,它时一个用逗号分隔的数组列表
.foo.bar .baz.bang, .bip.qux {
$selector: &; // 定义一个变量,变量值就是 父选择器标识符
content: $selector;
}
// 编译结果
.foo.bar .baz.bang, .bip.qux {
content: .foo.bar .baz.bang, .bip.qux; // 是一个用逗号分隔的数组列表
}
如果不存在父选择器,那么 & 的值就是 null,这也就意味着可以在 mixin中去判父元素是否存在:
@mixin does-parent-exist {
@if & { // 如果父选择器存在,那么就给父元素添加hover样式
&:hover {
color: red;
}
} @else {
a {
color: red;
}
}
}
div{ @include does-parent-exist(); }
// 编译结果 div:hover { color: red; }
4.10 变量定义 !default
可以在变量的结尾添加 !default
,给一个未通过 !default
声明赋值的变量赋值,此时,如果变量已经被赋值,不会再被重新赋值,但是如果变量还没有被赋值,则会被赋予新的值。
变量是 null 空值时将视为未被 !default
赋值。
$content: "First content";
$content: "Second content" !default; // $content在此之前已经被赋值,所以此处不会进行重新赋值
$new_content: null; // 变量值null被视为未赋值
$new_content: "First time reference" !default; // 所以此处可以重新赋值
#main {
content: $content;
new-content: $new_content;
}
// 编译结果
#main { content: "First content"; new-content: "First time reference"; }
!default一个重要的作用就是,如果我们引入的他人scss文件中的变量有默认值的设置,那么我们就可以很灵活的来修改这些默认值,只要在这些导入文件之前引入就一个配置scss文件即可,而无需修改他人的scss文件,例如:
// 在导入他人文件之前,修改默认值,而无需修改他人的scss文件
$theme: dark;
// 引入他人的scss文件
@import "config";
@import "variables";
@import "mixins";