Sass学习笔记1 - 基础篇

CSS预处理器 —— Sass学习笔记(基础)

参考

一、sass简介

1. 特色功能

  • 完全兼容 CSS3
  • 在 CSS 基础上增加 变量 (variables)、嵌套 (nested rules)、混合 (mixins)、导入 (inline imports) 等功能
  • 通过函数进行颜色值与属性值的运算
  • 提供控制指令 (control directives)等高级功能
  • 自定义输出格式

2. 语法格式

Sass 有两种语法格式。

首先是 SCSS (Sassy CSS)

  1. 这种格式以 .scss 作为拓展名
  2. 这种格式仅在 CSS3 语法的基础上进行拓展,所有 CSS3 语法在 SCSS 中都是通用的,同时加入 Sass 的特色功能。
  3. SCSS 也支持大多数 CSS hacks 写法以及浏览器前缀写法 (vendor-specific syntax),以及早期的 IE 滤镜写法。

另一种也是最早的 Sass 语法格式

  1. 被称为缩进格式 (Indented Sass) ,通常简称 "Sass",是一种简化格式。
  2. 这种格式以 .sass 作为拓展名
  3. 它使用 <u>“缩进” 代替 “花括号” 表示属性属于某个选择器</u>,用 <u>“换行” 代替 “分号” 分隔属性</u>,很多人认为这样做比 SCSS 更容易阅读,书写也更快速。
  4. 缩进格式也可以使用 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添加到系统环境变量。如下图:

01-install ruby.jpg

​ 安装完成后需测试安装有没有成功,运行CMD输入以下命令:

ruby -v
# 如安装成功会打印
ruby 2.6.4p104 (2019-08-28 revision 67798) [x64-mingw32]

3.2 Sass安装

Ruby 自带一个叫做 RubyGems 的系统,用来安装基于Ruby的软件。我们可以使用这个系统来 轻松地安装SassCompass。要安装最新版本的SassCompass,你需要输入下面的命令:

# 安装如下(如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 软件方式编译
  1. 推荐国产免费图形编译工具 Koala
  2. 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 允许将 <u>一套 CSS 样式</u> 嵌套进 <u>另一套样式中</u>,内层的样式将它外层的选择器 <u>作为父选择器</u>。嵌套功能 <u>避免了重复输入父选择器</u>,而且令复杂的 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使用这种后代选择器的方式生成这种连接。

用法一:作为普通的父选择器

​ <u>最常见的一种情况是当你为链接之类的元素写:hover这种伪类时</u>,你并不希望以后代选择器的方式连接。比如说,下面这种情况sass就无法正常工作:

article a {
  color: blue;
  :hover { color: red }
}

​ 这意味着color: red这条规则将会被应用到选择器article a :hoverarticle元素内链接的所有子元素在被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 }

用法三:用作选择器名称的占位符

​ <u>& 必须作为选择器的第一个字符</u>,其后可以跟随后缀生成复合的选择器,例如

#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.containerh1.containerh2.containerh3分别组合,然后将三者重新组合成一个群组选择器。然后对于内嵌在群组选择器内的嵌 套规则,处理方式也一样,将.container h1a.container h2a.container h3a再分别组合,然后重新组合成一个群组选择器,生成上面的普通css样式。

​ 缺点:虽然sass让你的样式表看上去很小,但实际生成的css却可能非常大,这会降低网站的速度。

4 子组合选择器 > 和同层组合选择器:+~

​ 这三个组合选择器 <u>必须和其他选择器配合使用</u>,以指定浏览器仅选择某种特定上下文中的元素。

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使用!来标识变量。改成是多半因为`!highlight-color`看起来太丑了),比如`highlight-colorsidebar-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; 
  1. 任何可以用作css属性值的赋值都可以用作sass的变量值。
  2. 变量声明后还未生效,只有当变量被引用后才生效
  3. 在声明变量时,变量值也可以引用其他变量。
$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-serifbold,在编译 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 的背景色不一样。

02-variable-array.png

用传统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 2px5px 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 () 3px1px 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 可以通过 opacifytransparentize 两个函数进行调整。

$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";
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343