目录
SCSS是一门编程语言,*.scss文件在特定环境下编译后能转化为原生的CSS代码。
SCSS相较于原生CSS的优势在于:SCSS的语法更符合编程思维,而原生CSS准确来说并非一门编程语言。
另外,SCSS完全兼容CSS语法,但是浏览器不支持识别scss文件。
VSCode安装插件:Live Sass Compiler
该插件是基于最新的dart sass开发的,因此支持scss最新语法特性,另外Live Sass Compiler还支持为样式添加针对不同浏览器兼容性处理。

插件配置
- { // settings.json根
- "liveSassCompile.settings.formats": [
- {
- "format": "expanded", // 生成文件的内容格式,取值只有两种expanded(展开格式,默认的)、compressed(压缩格式)
- "extensionName": ".css", // 生成文件的扩展名,建议当expanded时使用.css,当compressed时使用.min.css
- "savePath": "~/../css" // 生成文件的保存路径,~表示当前目录,~/..表示当前目录的上一级目录,~/../css 表示当前目录同级目录下的css文件夹;当前目录指的是被编译的scss文件所在的目录
- }
- ],
- /* 排除不进行编译检查目录 */
- "liveSassCompile.settings.excludeList":[
- "/**/node_modules/**",
- "/.vscode/**"
- ],
- /* 是否生成对应的map */
- "liveSassCompile.settings.generateMap": true,
- /* 是否添加兼容前缀 例如:-webkit- -moz- 等等 */
- "liveSassCompile.settings.autoprefix": [
- "> 1%",
- "last 2 versions"
- ]
- }
启动插件

这个插件的设置中savepath,即生成的css文件的保存路径,不支持动态路径,比如
- // savepath: "~/../css"
- // 编译前目录结构
- |- scss
- |- index.scss
- |- index
- |- header.scss
-
-
- // 实际编译后目录结构
- |- scss
- |- index.scss
- |- index
- |- header.scss
- |- css
- |- header.css
- |- css
- |- index.css
-
-
- // 期望编译后目录结构
- |- scss
- |- index.scss
- |- index
- |- header.scss
- |- css
- |- index.css
- |- index
- |- header.css
因此在使用live-sass-compiler插件编译scss时,我们定义scss文件时尽量只使用单级目录结构,而不要使用多级目录结构,比如
- // savepath: "~/../css"
- // 编译前目录结构
- |- scss
- |- index.scss
- |- header.scss
-
-
- // 编译后目录结构
- |- scss
- |- index.scss
- |- header.scss
- |- css
- |- index.css
- |- header.scss
如果项目本身就是采用多级、深层次的样式目录,那么可以考虑使用npm包:sass。
sass也是基于dart sass开发的,底层编译原理和live-compiler-sass插件一致。
sass安装方式如下(前提:本地有node环境)

sass进行监听编译
sass --watch ./scss:./css
sass命令参数含义:
其他命令参数可通过 sass --help 查看
此时,我们就可以实现多级目录的样式编译
- // sass -w ./scss:./css
- // 编译前目录结构
- |- scss
- |- index.scss
- |- index
- |- header.scss
-
-
- // 编译后目录结构
- |- scss
- |- index.scss
- |- index
- |- header.scss
- |- css
- |- index.css
- |- index
- |- header.css
但是,相较于live-compiler-sass而言,sass命令还少一个功能,那就是自动完成样式对不同浏览器的兼容性处理。这个只能后面在webpack打包时集成其他插件完成了。
如下原生CSS代码中,在进行后代选择的过程中,出现了大量的重复选择器的书写,比如.container重复了四次,.header重复了三次,这在编程思维中其实算冗余代码,但是按照CSS的语法逻辑必须这样写,才能明确表达后代关系
- .container {
- width: 1440px;
- margin: 0 auto;
- }
-
- .container .header {
- width: 100%;
- height: 50px;
- }
-
- .container .header ul {
- list-style: none;
- }
-
- .container .header ul li {
- padding: 10px;
- }
但是,如果使用SCSS语法来写的话,则可以类似于HTML标签的嵌套关系来表达后代层级关系
- .container {
- width: 1440px;
- margin: 0 auto;
-
- .header {
- width: 100%;
- height: 50px;
-
- ul {
- list-style: none;
-
- li {
- padding: 10px;
- }
- }
- }
- }

我们可以发现, SCSS的嵌套语法实现后代选择器的代码形式非常简洁,并且后代层级关系也足够清晰,而SCSS嵌套语法编译后生成的CSS代码与原生CSS代码完全一致。
SCSS的嵌套语法只能实现CSS的后代选择器,而不能实现子代选择器,兄弟选择器,以及父元素自身的伪类、伪元素选择器,比如下面CSS代码
- .header {
- width: 100%;
- height: 50px;
- }
-
- .header>ul {
- list-style: none;
- }
-
- .header>ul>li {
- padding: 10px;
- }
-
- .header>ul>li:hover {
- background-color: #000;
- color: #fff;
- }
此时,我们可以在SCSS嵌套中使用&来反引用父选择器
- .header {
- width: 100%;
- height: 50px;
-
- &>ul {
- list-style: none;
-
- &>li {
- padding: 10px;
-
- &:hover {
- background-color: #000;
- color: #fff;
- }
- }
- }
- }

嵌套语法中&反引用的父选择器不仅可以当成选择器使用,还可以当成选择器名字组成部分使用,如下CSS代码
- .header {
- width: 100%;
- height: 50px;
- overflow: hidden;
- }
- .header > .header-left {
- float: left;
- }
- .header > .header-right {
- float: right;
- }
如果用SCSS嵌套语法写的话:
- .header {
- width: 100%;
- height: 50px;
- overflow: hidden;
-
- &>&-left {
- float: left;
- }
-
- &>&-right {
- float: right;
- }
- }

在CSS中存在带来相同前缀的样式属性,比如font-size、font-weight、font-family、font-style,此时如果不采用复合写法font样式的话,则可以使用SCSS的属性嵌套,比如原生CSS代码如下
- .header {
- width: 100%;
- height: 50px;
-
- font-size: 20px;
- font-weight: bold;
- font-family: 'Courier New';
- font-style: italic;
- }
改用SCSS属性嵌套语法写的话
- .header {
- width: 100%;
- height: 50px;
-
- font: {
- size: 20px;
- weight: bold;
- family: 'Courier New';
- style: italic;
- }
- }

我觉得SCSS的属性嵌套语法倒显得有点鸡肋,不如使用复合写法。
SCSS的注释有两种:
SCSS编译为CSS后,会保留多行注释,不会保留单行注释。

CSS3的变量相当于一种特殊的样式属性,和样式属性的区别在于,定义变量属性时,其名字需要加前缀”--“,如下代码:
- html {
- --bgColor: black;
- }
CSS3变量需要通过var()函数来调用
- html>
- <html lang="en">
- <head>
- <meta charset="UTF-8" />
- <meta http-equiv="X-UA-Compatible" content="IE=edge" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>Documenttitle>
- <style>
- html {
- --bgColor: black;
- background-color: var(--bgColor);
- }
- style>
- head>
- <body>body>
- html>
选择器中定义的变量,可以在当前选择器中使用,也可以在选择器对应元素的后代元素的选择器中使用,我们可以理解为CSS3变量的作用域

如果想设定变量的作用域是全局,即当前网页中所有选择器都可以使用,则可以将变量定义在html选择器中,或者:root选择器中

:root选择器用于匹配文档的根元素,而文档的根元素就是html元素。
但是在CSS中,:root选择器的优先级高于html选择器,因为:root是伪类选择器,权重是10,而html是标签选择器,权重是1。

:root选择器的意义就在于定义全局变量,而html标签则更多的用于设置全局样式。
SCSS变量的名字以$开头,且$后面不能直接跟着数字,否则会编译报错。
SCSS变量可以既可以定义在选择器中,也可以不定义在选择器中,且SCSS变量不会被编译到CSS代码中,如下代码

SCSS变量可以直接当成样式属性值使用

SCSS变量也是有作用域概念的,没有定义在选择器中的变量就是全局变量,即所有选择器都可以使用的变量,定义在选择器中的变量就是局部变量,即局部变量只能给当前选择器以及其后代元素的选择器使用。
我们需要特别注意的是,CSS3变量的作用域是网页构建过程中确定的,即CSS3变量的作用域可以参照HTML标签的层级关系,比如:
单看CSS定义,我们无法知道.link和.container的关系,此时.link中使用.container中定义的变量--color是有可能发生问题的

但是一旦,将CSS样式和HTML结构结合

此时,.link就可以确定是.container的后代了,因此.link中使用.container的变量就顺理成章了。
但是,SCSS是独立编译的,无法参照HTML结构来确认样式之间的祖先后代关系,因此,SCSS中变量的使用,需要严格按照SCSS代码嵌套语法产生的祖先后代关系,即使定义在:root中的变量,也无法给非嵌套的后代选择器使用,比如

因此,我们提出在SCSS中将全局变量定义在选择器外面,而不是定义在:root选择器中。
SCSS变量有六种数据类型,分别是:
SCSS的变量不仅可以用做样式属性的值,还可以用作运算,流程控制,函数实参,混入实参,插值。
- $color: red !default;
-
- .header {
- color: $color;
- }
即在变量值后面加!default修饰。
额,我觉得这也是一个鸡肋。
SCSS运算包括:
等号运算有两个运算符:
等号运算可以用于比较任意类型的值,等号运算的结果为布尔值,等号运算常用于条件判断

等号运算其实也算是比较运算,但是SCSS中等号运算却被从比较运算中分离出来,这是因为等号运算可以比较任意类型的值,但是比较运算只能比较数字。

比较运算有如下运算符:
SCSS中比较运算符只能比较数字类型的值,不能比较其他类型。

如果比较非数字类型的值,SCSS编译会报错
SCSS逻辑运算符有三个:



需要注意的是,not运算符作用的条件需要用()包裹,避免歧义,如下例子:

SCSS中算术运算符包括:
算术运算只针对数字类型的值,SCSS中数字类型的值包括:不带单位的数字和带单位的数字,以及百分比数字。
其实,百分比数字可以看成有单位的数字,单位就是百分号。
运算情况包括:
+运算演示:

从上面运算可以看出:
-运算演示:

和+运算同理。
*运算演示:

*运算需要注意的是,不仅数字会相乘,单位也会相乘,因此*运算中,不能进行有单位数字和有单位数字的乘法,只能进行有单位数字和无单位数字,无单位数字和无单位数字的乘法。
/运算演示:

/ 运算需要注意,由于原生CSS中样式属性值经常使用 / 作为分隔符,比如
因此,SCSS中不会默认将 / 当成除号,只有如下情况才会将 / 当成除号

下面是有单位数字和无单位数字之间的除法运算情况

我们发现:
%运算演示:

求余运算符和百分号单位符号相同,因此求余运算时,我们必须要保证%求余运算符两边有空格,否则%会被当成百分号,如上例子。
其实,不仅仅是求余运算符两边要加空格,所有运算符两侧都建议加空格,避免歧义。
其实本质上来说,求余运算也算除法运算的一种,只是除法运算结果是商,求余运算结果是余数。

通过上面例子,我们可以发现,SCSS求余运算和除法运算存在区别:
+ 不仅是算术运算中的加法运算符,还是字符串运算中的字符串连接符,字符串连接有两种情况:

/ 在原生SCSS默认会被当成分隔符使用,但是当 / 两边中存在变量时,/ 就会被当成除法运算符,这会导致一些问题,如下代码:

我们期望编译结果是:font: 12px/1.5em Arial;
但是实际上,编译报错,因为 $font-size/$line-height 被当成了除法运算,但是除数和被除数的单位不同且无法转化,因此编译报错。
此时,我们可以使用SCSS的插值语法来避免 $font-size/$line-height 中 / 被当成除号

插值语法即 #{},插值语法可以将变量当成普通字符串输出。
插值语法可以在如下地方使用:

需要注意的是,插值语法中使用的变量必须要在使用前定义好,否则会报错


@for有两种使用方式:
两种方式都会从起始数值开始遍历,将遍历到的数值赋值给$i,区别在于,from...to不会遍历到结束数值,而只会遍历到结束数值前一个数,而from...through可以遍历到结束数值。

从上面例子中,我们可以发现,from 1 to 3 实际只遍历了1,2;而from 1 through 3 遍历了 1,2,3。
@while是另一种实现循环的方式,@while需要通过一个变量来控制循环次数,如下例中$count

@each用于遍历数组



map-has-key($map, $key):判断对象$map是否有键$key
map-get($map, $key) :获取对象$map下键$key对应的值

其实就是三元表达式

SCSS允许我们自定义函数,语法如下:
@function function-name([$val1, $val2, $val3, ...]) {
@return $res;
}
函数通过@function声明。
函数形参可以不传,可以传一个或多个,或者可变参数。
函数必须通过@return返回,且必须要有返回值。
我们可以将一些复杂逻辑定义为函数,来避免复杂逻辑的重复书写,而代替以函数调用。
比如移动端适配布局时,我们需要将元素的固定尺寸换算为rem尺寸,或者vw尺寸,此时我们就可以将换算逻辑定义为函数:

@mixin混入可以用于定义重复使用的样式,比如下面CSS代码
- .header {
- display: flex;
- justify-content: center;
- align-items: center;
-
- width: 500px;
- height: 100px;
- }
-
- .footer {
- display: flex;
- justify-content: center;
- align-items: center;
-
- width: 1400px;
- height: 50px;
- }
我们可以发现,.header和.footer存在相同重复的样式定义,此时SCSS可以将这段重复样式提取到混入@mixin中,再在.header和.footer中通过@inclue引入混入样式
- @mixin center {
- display: flex;
- justify-content: center;
- align-items: center;
- }
-
- .header {
- @include center;
-
- width: 500px;
- height: 100px;
- }
-
- .footer {
- @include center;
-
- width: 1400px;
- height: 50px;
- }

@mixin定义的样式支持接收外部参数,来作为内部样式的值,如下 @mixin flex($justify,$align)接收了两个参数$justify,$align,并且这两个参数作为了混入样式justify-content、align-item的值。
@mixin定义参数,需要通过@include来传递值,如下.header中@include flex(start,center)传了start给$justify,传了center给@align,而这种传参方式是顺序传参,即传参顺序严格按照@mixin定义的参数顺序来。

@include除了可以顺序传参外,还支持指定传参,即不按照@mixin定义的参数顺序传值

当@mixin样式需要的入参较多时,一般会设置一些默认值,来减轻@include传参压力,即对于有默认值的参数,@include可以不传值

有一些样式的值可以无穷传,比如backgroud:linear-gradient,此时我们无法通过手动定义无穷个@mixin参数来接收,需要使用可变参数来接收,可变参数和普通参数的区别在于,可变参数名字后面需要紧跟着三个点

上面代码中@mixin有两个参数,$direct是普通参数,$colors是可变参数,当@include传参时,第一个参数90deg传给了$direct,其余参数都传递给了$colors。
多个样式类,如果具有重复的样式属性名,但是样式属性值不同,此时可以将这些重复的样式提取到混入@mixin中定义,可以有效的减少代码。
在原生CSS中,我们总是将多个相关类中相同的样式提取出来形成一个基本类,比如下面代码中.btn样式类就是.btn-success、.btn-danger的基本样式类,.btn-success、.btn-danger使用时必须搭配.btn
- html>
- <html lang="en">
- <head>
- <meta charset="UTF-8" />
- <meta http-equiv="X-UA-Compatible" content="IE=edge" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>Documenttitle>
- <style>
- .btn {
- display: inline-block;
- padding: 6px 12px;
- font-size: 14px;
- font-weight: 400;
- line-height: 1.5em;
- text-align: center;
- vertical-align: middle;
- cursor: pointer;
- border: 1px solid transparent;
- border-radius: 4px;
- }
-
- .btn-success {
- color: #fff;
- background-color: #5cb85c;
- border-color: #5cb85c;
- }
-
- .btn-danger {
- color: #fff;
- background-color: #d9534f;
- border-color: #d43f3a;
- }
- style>
- head>
- <body>
- <a class="btn btn-success">成功a>
- <a class="btn btn-danger">失败a>
- body>
- html>
当然,为了使用简单而言,上面样式定义可以更改为
- html>
- <html lang="en">
- <head>
- <meta charset="UTF-8" />
- <meta http-equiv="X-UA-Compatible" content="IE=edge" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>Documenttitle>
- <style>
- .btn, .btn-success, .btn-danger {
- display: inline-block;
- padding: 6px 12px;
- font-size: 14px;
- font-weight: 400;
- line-height: 1.5em;
- text-align: center;
- vertical-align: middle;
- cursor: pointer;
- border: 1px solid transparent;
- border-radius: 4px;
- }
-
- .btn-success {
- color: #fff;
- background-color: #5cb85c;
- border-color: #5cb85c;
- }
-
- .btn-danger {
- color: #fff;
- background-color: #d9534f;
- border-color: #d43f3a;
- }
- style>
- head>
- <body>
- <a class="btn-success">成功a>
- <a class="btn-danger">失败a>
- body>
- html>
这样在使用时,.btn-success、.btn-danger就可以单独使用了。
而在SCSS中,.btn-success、.btn-danger就是继承.btn的公共基础样式,语法如下:
- .btn {
- display: inline-block;
- padding: 6px 12px;
- font-size: 14px;
- font-weight: 400;
- line-height: 1.5em;
- text-align: center;
- vertical-align: middle;
- cursor: pointer;
- border: 1px solid transparent;
- border-radius: 4px;
- }
-
- .btn-success {
- @extend .btn;
-
- color: #fff;
- background-color: #5cb85c;
- border-color: #5cb85c;
- }
-
- .btn-danger {
- @extend .btn;
-
- color: #fff;
- background-color: #d9534f;
- border-color: #d43f3a;
- }

可以发现,编译结果和预期一致。
- .btn, .btn-danger, .btn-success {
- display: inline-block;
- padding: 6px 12px;
- font-size: 14px;
- font-weight: 400;
- line-height: 1.5em;
- text-align: center;
- vertical-align: middle;
- cursor: pointer;
- border: 1px solid transparent;
- border-radius: 4px;
- }
-
- .btn-success {
- color: #fff;
- background-color: #5cb85c;
- border-color: #5cb85c;
- }
-
- .btn-danger {
- color: #fff;
- background-color: #d9534f;
- border-color: #d43f3a;
- }
上面CSS代码中,其实还有优化的空间,那就是.btn定义显得可有可无了,也就是说,不定义.btn,最终效果也一致。
- .btn-danger, .btn-success {
- display: inline-block;
- padding: 6px 12px;
- font-size: 14px;
- font-weight: 400;
- line-height: 1.5em;
- text-align: center;
- vertical-align: middle;
- cursor: pointer;
- border: 1px solid transparent;
- border-radius: 4px;
- }
-
- .btn-success {
- color: #fff;
- background-color: #5cb85c;
- border-color: #5cb85c;
- }
-
- .btn-danger {
- color: #fff;
- background-color: #d9534f;
- border-color: #d43f3a;
- }
而在SCSS中,我们可以使用占位符%,来定义公共基础样式,而占位符选择器不会被编译到CSS中
- %btn {
- display: inline-block;
- padding: 6px 12px;
- font-size: 14px;
- font-weight: 400;
- line-height: 1.5em;
- text-align: center;
- vertical-align: middle;
- cursor: pointer;
- border: 1px solid transparent;
- border-radius: 4px;
- }
-
- .btn-success {
- @extend %btn;
-
- color: #fff;
- background-color: #5cb85c;
- border-color: #5cb85c;
- }
-
- .btn-danger {
- @extend %btn;
-
- color: #fff;
- background-color: #d9534f;
- border-color: #d43f3a;
- }

占位符继承,相当于将占位符替换为继承类,比如.btn-success中@extend %btn,相当于将%btn类替换为.btn-success类,比如下面代码

一个选择器中可以继承多个公共基础样式,即一个选择器中可以多次@extend多个样式类

对比SCSS和CSS来看,SCSS继承语法的语义更加明确,而CSS的语义就略显单薄。
继承的是多个样式共同使用的基础样式,样式内容相同,继承可以避免在多个样式内部书写重复基础样式。
混入的是多个样式共同使用的公共样式,样式属性相同,但是值可能不同,利用混入的传参机制,可以避免在多个样式中书写重复的样式属性。
原生CSS除了可以通过link标签属性在HTML引入外部CSS文件,还可以通过@import在HTML的style标签体中、或者CSS文件中引入外部CSS文件,当然这不是link和@import最大的区别,二者最大的区别在于:
SCSS的@import可以在scss文件A中导入其他scss文件B,被导入的scss文件B中的内容会被合并到当前scss文件A内容,一起编译到CSS文件中。而对于混入,占位符这些不会被编译到CSS中的B的内容,A可以随意使用。

@import导入scss文件可以省略其.scss后缀。

另外我们发现当index.scss @import "comm"时,会导致comm.scss也被编译,那么如果我们不想comm.scss被编译呢,该如何处理呢?
此时只需要将comm.scss重命名为_comm.scss即可,其余代码不变

@import "comm" 会优先去找_comm.scss,找不到再去找comm.scss,如果再找不到就编译报错。
如果目录下同时存在comm.scss和_comm.scss,则@import "comm"也会编译报错:

如果SCSS中@import出现如下情况:
都会被当成原生CSS的@import导入语法

SCSS的@import是非模块化的,即SCSS的@import支持重复导入同一个scss文件,并且多次导入的同一个scss文件的内容也会被多次合并,最终编译到CSS文件中

SCSS的@use是@import的升级版,具有@import的大部分能力,但是存在如下不同:
@use支持缓存导入的文件,将导入的文件当成一个模块,后面即使重复导入,也不会合入多次,而是只合入一次。

@use多次导入同一个模块时,则需要为每次导入的模块定义别名,否则无法编译通过,如上图中index.scss中,@use "comm" as c1 中 as c1 就是定义模块别名。
一般情况下,我们不会多次@use同一个模块,而是只导入一次,此时就不需要强制为模块定义别名,导入文件的名字就是模块的默认别名。
我们可以通过模块别名来调用scss模块中混入、占位符、变量等

另外,@use还支持导入css文件,并且会合入导入的css文件的内容,这是和SCSS的@import不同的另一个地方。

由于@use的提出,每一个scss文件都可以被当成一个模块被导入,并且导入后可以被随意使用模块中定义的变量,如果某个变量只是模块内部使用,而不像被外部使用,此时可以将变量定义为私有的,即在变量的$符号和变量名之间加一个 - 连接

有这样一种常见,a.scss中导入了b.scss,b.scss中导入了c.scss,并且a.scss中需要使用c.scss中的变量
如果我们使用@use导入,则无法实现上面需求

如果想在a.scss中访问c.scss中的变量$c,则需要在a中@use "c".
混入、占位符等同理。
上面的需求其实是一种模块转发,SCSS支持通过@forward来转发模块中的变量、混入、占位符等
上面b.scss中@forward转发了c.scss中的变量、混入、占位符等,而a.scss中@use模块导入了b.scss,理论上,a.scss中可以直接使用$c、@mixin flex、%btn等,但是实际上,却需要通过b.scss的模块别名来调用。