Skip to content

概念

SCSS(Sassy CSS)是 Sass 预处理器的主流语法形式,是原生 CSS 的超集(类似于 JS 与 TS 的关系)。这也就意味着:

  • 所有合法的 CSS 代码直接复制到 SCSS 文件中都能正常运行
  • 在此基础上,SCSS 增加了变量、嵌套、Mixin、继承等编程化特性
  • 浏览器无法直接识别 SCSS,必须通过编译工具(如 dart-sass)转换成普通 CSS 才能生效

环境

SCSS 编译依赖 Node.js,所以需要终端上具备该环境

sh
# 安装编译工具
npm install -g sass

# 编译文件:sass 源文件.scss 输出文件.css
sass src/style.scss dist/style.css

# 实时监听(修改 SCSS 自动编译)
sass --watch src/style.scss:dist/style.css # 监听文件
sass --watch src/styles:dist/styles # # 监听整个目录

目录结构

txt
scss/
├── _variables.scss  // 变量
├── _mixins.scss     // 混合
├── _buttons.scss    // 按钮样式
└── index.scss       // 入口文件(对外暴露)

用于把不同的数据分割出去,不至于让一个文件变得臃肿。再局部文件写完数据后再入口文件进行导入

scss
// scss/index.scss
@forward "variables";   // 转发变量
@forward "mixins";      // 转发混合
@forward "buttons";     // 转发按钮样式

此时别的地方只需要导入这一个 @use "./scss" as ui; 即可使用

SCSS 中,文件名以 _ 开头(如 _variables.scss),表示这是一个局部文件(Partial File);它的核心特性是:

  • 不会被编译器单独编译成 CSS 文件
  • 只能被其他 SCSS 文件通过 @use/@forward/@import 导入使用

在处理导入路径时,会自动匹配带 _ 前缀的文件,此时就可以省略下划线的书写

注释

scss
// 单行注释 —— 编译后会被删除(仅保留在 SCSS 源码)
/* 多行注释 —— 编译后保留在 CSS 文件中(可用于版权声明) */
/*! 强制保留注释 —— 即使压缩 CSS 也不会删除 */

嵌套

核心作用是让 CSS 选择器的层级和 HTML 结构一一对应,不用重复写父选择器

原生 CSS:

css
/* HTML 结构:<nav><ul><li><a></a></li></ul></nav> */
nav {
	background: #f5f5f5;
}
nav ul {
	list-style: none;
	margin: 0;
	padding: 0;
}
nav ul li {
	float: left;
	padding: 0 15px;
}
nav ul li a {
	color: #333;
	text-decoration: none;
}
nav ul li a:hover {
	color: #165DFF;
}

SCSS 嵌套:

scss
nav {
	background: #f5f5f5;

	// 嵌套 ul(父选择器是 nav)
	ul {
		list-style: none;
		margin: 0;
		padding: 0;

		// 嵌套 li(父选择器是 nav ul)
		li {
			float: left;
			padding: 0 15px;

			// 嵌套 a(父选择器是 nav ul li)
			a {
				color: #333;
				text-decoration: none;

				// 嵌套 hover 伪类
				&:hover {
					color: #165DFF;
				}
			}
		}
	}
}

编译过后最终结果会跟 原生CSS 一摸一样。子选择器会自动拼接父选择器 + 空格 + 子选择器

在嵌套的时候,选择器跟原生的 CSS 选择器是一致的,如:类选择器、标签选择器、ID选择器...

& 符号

& 是嵌套里用于替换父选择器的占位符,它会完全替代父选择器

scss
.btn {
	padding: 8px 16px;
	background: #165DFF;
	color: #fff;

	// & 代表 .btn → 编译后:.btn:hover
	&:hover {
		background: darken(#165DFF, 8%);
	}

	// & 代表 .btn → 编译后:.btn:active
	&:active {
		background: lighten(#165DFF, 8%);
	}
}

也可以用来拼接 BEM 格式修饰符

scss
// 块:card
.card {
	width: 300px;

	// 元素:card__header(& 代表 .card)
	&__header {
		padding: 10px;
		border-bottom: 1px solid #eee;
	}

	// 修饰符:card--large(& 代表 .card)
	&--large {
		width: 400px;
	}

	// 修饰符+元素:card--large .card__header
	&--large &__header {
		padding: 15px;
	}
}

// ---------- 编译后 -----------
.card { width: 300px; }
.card__header { padding: 10px; border-bottom: 1px solid #eee; }
.card--large { width: 400px; }
.card--large .card__header { padding: 15px; }

BEM 格式:块__元素--修饰符。一种常用的命名规范

插值语法

语法:#{}

scss
// 选择器插值
$prefix: "ui";
.#{$prefix}-btn { /* .ui-btn */ }

// 属性名插值
$prop: "color";
.box {
    #{$prop}: #333; /* color: #333 */
}

// 数值+单位插值
$unit: "px";
.box {
    width: 100#{$unit}; /* 100px */
}

属性嵌套

对于拥有相同前缀的 CSS 属性也可以进行嵌套

scss
.box {

	// 字体属性嵌套(编译后:font-size: 14px; font-weight: bold;)
	font: {
		size: 14px;
		weight: bold;
	}

	// 内边距嵌套(编译后:padding-top: 10px; padding-bottom: 10px;)
	padding: {
		top: 10px;
		bottom: 10px;
		left: 20px;
		right: 20px;
	}

	// 边框嵌套(编译后:border-width: 1px; border-style: solid; border-color: #eee;)
	border: {
		width: 1px;
		style: solid;
		color: #eee;
	}
}

父选择器前置

本质是把原本的子选择器变成 “父选择器”,原本的父选择器变成 “子选择器”

scss
// 对于 .box 提前定义了基础样式
.box {
	width: 100px;
	height: 100px;
}

// 但是再某些情况下,我们需要对嵌套在特定容器中的 .box 进行特殊样式设置
.container {
	.box {
		width: 200px;
	}
}

上述写法的问题:.box 的基础样式和 “容器限定样式” 分开了,代码不集中。用 & 前置可以把容器限定样式写在 .box 自己的层级里

scss
.box {
	// 基础样式(所有 .box 都生效)
	width: 100px;
	height: 100px;

	// 父选择器前置:.container &;编译后:.container .box
	.container & {
		width: 200px; // 只有在 .container 里的 .box 宽度变成 200px
	}
}

跳出嵌套

语法:@at-root [选择器]

scss
// 场景:深层嵌套中,需要给某个选择器单独提级
.card {
    width: 300px;

    .body {
        padding: 10px;

        // @at-root 让 .card-link 跳出嵌套,编译后直接是 .card-link,而非 .card .body .card-link
        @at-root .card-link {
            color: #165DFF;
        }
    }
}

// 编译后结果
.card { width: 300px; }
.card .body { padding: 10px; }
.card-link { color: #165DFF; }

// @at-root with #{}(带父选择器跳出)
.card {
    .body {
        // 编译后是 .card .card-special,而非 .card .body .card-special
        @at-root #{&}-special {
            border: 1px solid #eee;
        }
    }
}

变量

语法:$变量名: 变量值;

scss
// 颜色(最常用:主题色、辅助色)
$primary-color: #165DFF; // 主色调

// 尺寸(间距、圆角、字体大小)
$base-font-size: 14px;   // 基础字体大小

// 字体(字体族)
$base-font-family: "Microsoft Yahei", sans-serif;

// 布尔值/数字(用于逻辑判断)
$is-dark-mode: false;
$max-width: 1200;

定义好的变量,在任何需要写值的地方直接用 $变量名 替代即可

scss
background-color: $primary-color; // 主色调

作用域

作用域决定了变量能在什么被使用,分为全局局部两个作用域

全局

定义位置:在所有选择器、Mixin 之外(最好写在单独的 _variables.scss 文件)

作用范围:整个 SCSS 文件(及导入该文件的其他文件)都能使用

scss
$global-color: #165DFF;

.box1 {
	color: $global-color;
}

.box2 {
	color: $global-color;
}

局部

定义位置:在选择器、Mixin 内部

作用范围:仅在定义它的 “大括号内” 生效,外部无法使用

scss
.box1 {
	// 父级不可用
	color: $global-color;

	.box2 {
		// 定义局部变量
		$global-color: #165DFF;

		// 同级可用
		color: $global-color;

		.box3 {
			// 子集可用
			color: $global-color;
		}
	}
}

特殊(全局)

若是想让局部变量能够被全局使用,则只需要再变量末尾加入 !global 即可

scss
$global-color: #165DFF !global;

尽量避免使用 !global 避免造成全局变量污染

特殊(默认)

!default 作用为给变量设置默认值,若该变量已被定义,则使用已定义的值;若未定义,则使用默认值

语法:$变量名: 默认值 !default;

scss
// _button.scss(按钮组件的变量文件)
$button-primary-color: #165DFF !default; // 默认主色调
$button-padding: 8px 16px !default;     // 默认内边距

// 按钮样式
.btn-primary {
  background: $button-primary-color;
  padding: $button-padding;
}

// ----------- 分割 -----------------

// 使用者的 index.scss
// 第一步:自定义变量(覆盖默认值)
$button-primary-color: #F53F3F; // 改成红色
$button-padding: 10px 20px;     // 加大内边距

// 第二步:导入按钮组件
@import "_button";

// 最终按钮的主色调是红色,内边距是10px 20px

Map

语法:$map名: (键1: 值1, 键2: 值2, ...);

scss
// 示例:主题色 Map
$theme-colors: (
    primary: #165DFF,
    success: #00B42A,
    warning: #FF7D00,
    danger: #F53F3F
);

访问 Map 中的值

scss
.btn-primary {
    background: map-get($theme-colors, primary); // #165DFF
}

常用函数:

函数名作用
map-get($map, $key)获取指定键的值
map-keys($map)获取所有键(返回列表)
map-values($map)获取所有值(返回列表)
map-has-key($map, $key)判断是否有某个键
map-merge($map1, $map2)合并两个 Map

列表

定义:直接通过空格或者逗号来创建

scss
$list1: 10px 20px 30px; // 空格分隔
$list2: (10px, 20px, 30px); // 逗号分隔
函数名作用
length($list)获取列表长度(值的个数)
nth($list, $index)获取列表中第 N 个值(索引从 1 开始,而非 0)
set-nth($list, $index, $value)修改列表中第 N 个值
append($list, $value)向列表末尾添加值
prepend($list, $value)向列表开头添加值
join($list1, $list2)合并两个列表
index($list, $value)查找值在列表中的索引(无则返回 null)
list-separator($list)获取列表的分隔符(comma/space)
zip($list1, $list2)按索引配对多个列表(生成嵌套列表)

原生变量

scss
// SCSS 变量转 CSS 变量(支持运行时动态修改)
$primary-color: #165DFF;

:root {
    --primary-color: #{$primary-color}; // 插值注入 CSS 变量
    --font-size-base: #{$base-font-size};
}

// 使用 CSS 变量
.btn {
    background: var(--primary-color);
    // 结合 SCSS 运算 + CSS 变量
    padding: calc(var(--font-size-base) * 2);
}

混合

Mixin(混合)核心作用是封装可复用的样式片段

使用 @mixin 混合名(形参) { 样式表 } 来定义混合,然后使用 @include 混合名(实参) 来调用。参数不是必填项;如果有多个参数中间使用 逗号 分割,并且参数可以使用 冒号 来定义默认参数

场景:需要三种按钮,他们的基础样式都是一样的,仅仅是背景颜色有所不同

scss
// 定义 Mixin:封装基础按钮样式,接收“背景色”参数
@mixin button-style($bg-color) {
	padding: 8px 16px;
	border: none;
	border-radius: 4px;
	color: #fff;
	background: $bg-color;
}

// 调用 Mixin:传不同背景色,一行搞定
.btn-primary {
	@include button-style(#165DFF);
}

.btn-success {
	@include button-style(#00B42A);
}

.btn-warning {
	@include button-style(#FF7D00);
}

可变参数

通过再最后一个变量的末尾添加 ... 来实现可变参数

scss
// 定义:... 表示可变参数
@mixin box-shadow($color, $shadows...) {
    color: $color;
    box-shadow: $shadows;
}

// 调用:传任意数量参数
.box1 {
    @include box-shadow(#165DFF, 0 2px 4px rgba(0, 0, 0, 0.1));
}

.box2 {
    @include box-shadow(#165DFF, 0 2px 4px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.2));
}

传递样式块

@content 允许在调用 Mixin 时,额外传入一段样式,相当于给 Mixin 加 “自定义扩展”

scss
// 定义:响应式 Mixin,用 @content 接收自定义样式
@mixin responsive($breakpoint) {
	@if $breakpoint ==mobile {
		@media (max-width: 768px) {
			@content; // 接收调用时传入的样式
		}
	}

	@else if $breakpoint ==tablet {
		@media (min-width: 769px) and (max-width: 992px) {
			@content;
		}
	}

	@else if $breakpoint ==pc {
		@media (min-width: 993px) {
			@content;
		}
	}
}

// 调用:传入自定义样式(不同设备的宽度)
.box {
	width: 100%;

	// 移动端:宽度 90%
	@include responsive(mobile) {
		width: 90%;
		padding: 10px;
	}

	// 平板:宽度 80%
	@include responsive(tablet) {
		width: 80%;
		padding: 15px;
	}

	// PC:宽度 1200px
	@include responsive(pc) {
		width: 1200px;
		padding: 20px;
	}
}

继承

@extend(继承)是 SCSS 中另一种样式复用方式,核心作用是让一个选择器完全继承另一个选择器的所有样式

scss
// 定义被继承的通用样式(基础文本样式)
.text-common {
	font-family: "Microsoft Yahei";
	line-height: 1.5;
	color: #333;
	margin: 0;
	padding: 0;
}

// 继承通用样式(标题1)
.title-h1 {
	@extend .text-common; // 继承 .text-common 的所有样式
	font-size: 24px;
	font-weight: bold;
}

// 继承通用样式(标题2)
.title-h2 {
	@extend .text-common; // 继承 .text-common 的所有样式
	font-size: 20px;
	font-weight: 600;
}

// ----------- 分割(编译后结果) ----------

.text-common,
.title-h1,
.title-h2 {
	font-family: "Microsoft Yahei";
	line-height: 1.5;
	color: #333;
	margin: 0;
	padding: 0;
}

.title-h1 {
	font-size: 24px;
	font-weight: bold;
}

.title-h2 {
	font-size: 20px;
	font-weight: 600;
}

占位符选择器

如果被继承的选择器(如 .text-common)只是 “复用模板”,不需要在 HTML 中直接使用,用 占位符选择器 % 替代类选择器,编译后不会生成这个 “模板选择器”,减少冗余

scss
// 占位符选择器:仅用于继承,编译后不会生成 .text-common
%text-common {
	font-family: "Microsoft Yahei";
	line-height: 1.5;
	color: #333;
}

.title-h1 {
	@extend %text-common;
	font-size: 24px;
}

如果被继承的是嵌套选择器,会把所有子选择器也继承。也就是说,最终的编译结果会连嵌套的子集也生成一份继承数据

运算

SCSS 的运算功能的核心作用是对数值、颜色、字符串等进行计算,动态生成样式值。原生 CSS 也能够使用 calc() 进行计算,但是语法受限(如单位必须明确);而 SCSS 则是能够直接进行运算,还能结合变量、函数进行辅助

scss
$base-width: 100px;
.box {
	width: $base-width - 20; // 自动补单位,结果 80px
}

// 原生 CSS calc()
.box {
	width: calc(100px - 20px); // 必须写全单位
}

语法

SCSS 支持 +-*/% 五种基础运算符,还支持比较运算(><>=<===!=)和逻辑运算(andornot

规则:

  1. 单位自动适配:只要一个值有单位,结果会自动继承单位(如 100px - 20 = 80px
  2. 运算优先级:和数学一致(先乘除后加减,括号优先)
  3. 除法注意/ 在 SCSS 中默认是 “分隔符”(如:font: 14px/1.5; 表示字体大小14px,行高1.5),需满足以下条件才会被识别为除法:
    • 数值被括号包裹:(100px / 2)
    • 数值是变量 / 函数返回值:$width / 2
    • 配合其他运算符:100px + 20px / 2

数值

scss
// 定义变量
$base-width: 1200px;
$gutter: 20px;
$column-count: 4;

// 宽度计算:总宽度 - 左右间距
.container {
	width: $base-width - $gutter * 2; // 1200 - 40 = 1160px
	margin: 0 auto;
}

// 列宽计算:均分列数(除法需括号)
.column {
	width: ($base-width / $column-count); // 1200 / 4 = 300px
	margin-right: $gutter;

	// 最后一列去掉右边距(逻辑判断+运算)
	&:last-child {
		margin-right: 0;
	}
}

// 百分比运算
.box {
	width: 100% - 20%; // 80%
	height: (500px / 2); // 250px
}

如果是不同单位之间进行运算,那么 SCSS 会直接报错。所以得使用 calc(),SCSS 会保留 calc(),交给浏览器计算

scss
.box {
    width: calc(100px + 20em); // SCSS 会保留 calc(),交给浏览器计算
}

颜色

scss
$primary-color: #165DFF; // 十六进制颜色

// 直接运算(RGB 分量加减)
.btn-hover {
	background: $primary-color + #111; // 加深颜色(#175EFF)
}

.btn-light {
	background: $primary-color - #222; // 减淡颜色(#155CDE)
}

// 用内置颜色函数(精准控制)
.btn-dark {
	background: darken($primary-color, 10%); // 加深10%
}

.btn-bright {
	background: lighten($primary-color, 10%); // 减淡10%
}

字符串

scss
// 拼接图片路径
$img-path: "../images/";

.logo {
	background: url($img-path + "logo.png"); // url("../images/logo.png")
}

逻辑

scss
// 定义变量
$is-dark-mode: true;
$max-width: 1200;

// 条件运算
.container {
	@if $is-dark-mode {
		background: #333;
		color: #fff;
	}

	@else {
		background: #fff;
		color: #333;
	}

	// 逻辑判断(and/or/not)
	@if $max-width >1000 and $is-dark-mode {
		padding: 30px;
	}
}

内置函数

数学

函数名作用示例结果
round($n)四舍五入round(3.2)3
ceil($n)向上取整ceil(3.1)4
floor($n)向下取整floor(3.9)3
abs($n)取绝对值abs(-20)20
min($n1, $n2)取最小值min(10, 20)10
max($n1, $n2)取最大值max(10, 20)20
random($limit)随机数random()
random($limit)
无参:生成 0-1 之间的随机数(不含 1)
有参:生成 1 到 N 之间的随机整数(包含两端)

颜色

函数名作用示例
darken($color, $n)加深颜色($n 是百分比)darken(#165DFF, 10%)
lighten($color, $n)减淡颜色($n 是百分比)lighten(#165DFF, 10%)
rgba($color, $a)给颜色加透明度($a 是 0-1)rgba(#165DFF, 0.5)
mix($c1, $c2, $n)混合两种颜色($n 是占比)mix(#165DFF, #fff, 50%)

类型判断与转换

SCSS 提供 type-of() 等函数判断值类型,避免运算 / 传参错误

scss
// 类型判断
$val1: 100px;
$val2: #fff;
$val3: (1, 2, 3);

@debug type-of($val1); // number(数值)
@debug type-of($val2); // color(颜色)
@debug type-of($val3); // list(列表)

// 类型转换(常用)
$num: 100;
$px-num: $num * 1px; // 100px(数字转带单位数值)

$color-hex: #165DFF;
$color-rgb: rgb($color-hex); // rgb(22, 93, 255)(十六进制转RGB)

拆分/导入

核心是 @use(导入)、@forward(导出 / 转发),以及弃用的 @import,它能把样式拆分成多个文件(如变量、Mixin、工具函数),按需导入 / 对外暴露,避免代码冗余和变量污染

指令核心作用描述
@use导入其他 SCSS 文件,生成独立命名空间现代 SCSS 首选(替代 @import)
@forward转发 / 导出其他 SCSS 文件的内容封装公共模块时用(对外暴露接口)
@import导入文件,变量全局污染官方弃用(仅兼容老项目)

导出

scss
// 语法1:基础转发
@forward "variables"; // 转发 _variables.scss 的所有内容

// 语法2:转发时隐藏指定内容(对外不暴露)
@forward "variables" hide $private-color, @private-mixin;

// 语法3:转发时添加前缀(避免命名冲突)
@forward "buttons" as btn-*; // 所有 Mixin/变量加 btn- 前缀

导入 use

@use 导入:

scss
// 默认别名(文件名)
@use "文件路径"; // 如 @use "variables" → 别名是 variables

// 自定义别名
@use "文件路径" as 别名; // 如 @use "variables" as v;
// 简化别名
@use "variables" as *; // 省略别名,直接调用变量(但会失去命名空间保护,易冲突)

// 导入时覆盖默认变量
@use "variables" with (
    $primary-color: #F53F3F,
    $button-padding: 10px 20px
);

路径规则:

  • 省略文件后缀(.scss)和开头的 _(如 _variables.scss → 写 variables
  • 相对路径:@use "./src/variables"(当前目录下的 src 文件夹)

使用时则是 别名.xxx 来调用

导入 import

scss
@import "_variables.scss"; // 可省略 _ 和 .scss → @import "variables";

问题:

  • 变量全局污染:导入后变量 / Mixin 直接全局可用,多个文件重名会覆盖;
  • 重复编译:多次导入同一文件,会重复编译样式;
  • 无命名空间:无法隔离不同文件的内容。

指令

for 循环

scss
// 列表:多个值的集合
$sizes: 12px, 14px, 16px, 18px;

// For 循环列表,生成字体类
@for $i from 1 through length($sizes) {
	.text-#{$i} {
		font-size: nth($sizes, $i); // nth:取列表第n个值
	}
}

// ---------- 分割 -----------

// 编译后生成
.text-1 { font-size: 12px; }
.text-2 { font-size: 14px; }
.text-3 { font-size: 16px; }
.text-4 { font-size: 18px; }

each 循环

scss
// 遍历列表
$sizes: small 12px, medium 16px, large 20px;
@each $name, $size in $sizes {
	.text-#{$name} {
		font-size: $size;
	}
}

// 遍历 Map(主题色)
$theme-colors: (
	primary: #165DFF,
	success: #00B42A
);
@each $key, $color in $theme-colors {
	.btn-#{$key} {
		background: $color;
	}
}

while 循环

scss
$i: 1;
@while $i <=4 {
	.item-#{$i} {
		margin-left: $i * 10px;
	}
	$i: $i + 1;
}

函数

scss
// 自定义函数:计算响应式字体大小(vw 适配)
@function rem($px, $base: 16px) {
	@return ($px / $base) * 1rem;
}

@function vw($px, $viewport: 375px) {
	@return ($px / $viewport) * 100vw;
}

.box {
	font-size: rem(24px); // 1.5rem
	width: vw(300px); // 80vw(基于 375px 视口)
}