Sass/SCSS 常用语法
大白话解释: Sass 就是"增强版 CSS"。原生 CSS 没有变量、没有嵌套、没有复用机制,写起来很麻烦。Sass 在 CSS 的基础上加了这些功能,让样式代码更易维护。
为什么用 Sass?
- 变量:定义一次颜色、字号,到处复用,修改时只改一处
- 嵌套:像 HTML 一样嵌套写样式,层级清晰
- Mixin:把常用的样式组合打包,像函数一样复用
- 函数:颜色计算、单位转换等,不用手写
- 模块化:把样式拆成多个文件,最后合并成一个
Sass vs SCSS 怎么选?
- SCSS(推荐):语法和 CSS 一样,只是加了变量、嵌套等功能,学习成本低
- Sass(缩进语法):没有大括号和分号,更简洁,但需要适应
Sass 是最流行的 CSS 预处理器,有两种语法:SCSS(推荐,兼容 CSS)和 Sass(缩进语法)。
安装
bash
# 全局安装
npm install -g sass
# 项目安装(推荐)
npm install -D sass
# 编译
sass input.scss output.css
# 压缩编译
sass --style=compressed input.scss output.min.css
# 监听变化
sass --watch src:dist
# 监听单个文件
sass --watch input.scss:output.css变量
基本用法
scss
// 用 $ 定义变量(Sass 用 $,Less 用 @,CSS 原生用 --)
$primary: #1890ff; // 主色:按钮、链接等核心颜色
$success: #52c41a; // 成功色:成功状态提示
$danger: #ff4d4f; // 危险色:错误、删除操作
$warning: #faad14; // 警告色:警告状态提示
$info: #1890ff; // 信息色:一般提示
// 间距:统一管理组件内外边距,方便全局调整
$spacing-xs: 4px;
$spacing-sm: 8px;
$spacing-md: 16px;
$spacing-lg: 24px;
$spacing-xl: 32px;
$spacing-xxl: 48px;
// 字体:集中定义字号和字体栈,保持排版一致性
$font-size-xs: 12px;
$font-size-sm: 13px;
$font-size-base: 14px;
$font-size-lg: 16px;
$font-size-xl: 20px;
$font-size-xxl: 24px;
$font-size-h1: 32px;
$font-family-base: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial;
$font-family-code: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
// 圆角:按钮、卡片等组件的圆角统一管理
$border-radius-sm: 2px;
$border-radius-base: 4px;
$border-radius-lg: 8px;
$border-radius-xl: 12px;
$border-radius-round: 50%;
// 阴影:不同层级的投影效果
$box-shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.09);
$box-shadow-base: 0 4px 16px rgba(0, 0, 0, 0.12);
$box-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.15);
// z-index 层级:统一管理层叠顺序,避免 z-index 混乱
$z-index-dropdown: 1000;
$z-index-sticky: 1020;
$z-index-fixed: 1030;
$z-index-modal-backdrop: 1040;
$z-index-modal: 1050;
$z-index-tooltip: 1060;
// 断点:响应式布局的屏幕宽度分界点
$breakpoint-sm: 576px; // 手机横屏
$breakpoint-md: 768px; // 平板
$breakpoint-lg: 1024px; // 小桌面
$breakpoint-xl: 1280px; // 大桌面
$breakpoint-xxl: 1600px; // 超大屏
// 使用变量:直接用 $变量名 引用
.button {
background: $primary;
padding: $spacing-sm $spacing-md;
font-size: $font-size-base;
border-radius: $border-radius-base;
box-shadow: $box-shadow-sm;
}默认变量
scss
// 使用 !default,可以被外部覆盖
$primary: #1890ff !default;
$border-color: #ddd !default;
$border-width: 1px !default;
$border-style: solid !default;
$border-radius-base: 4px !default;
.button {
background: $primary;
border-radius: $border-radius-base;
}
.border {
border: $border-width $border-style $border-color;
}
// 覆盖默认变量(必须在 @use 之前)
// 只覆盖 $primary 和 $border-radius-base,其他 !default 变量保持原值
@use './variables' with (
$primary: #722ed1,
$border-radius-base: 8px
);css
/* 编译后:被覆盖的变量使用新值,未覆盖的保持 !default 默认值 */
.button {
background: #722ed1; /* $primary 被覆盖为 #722ed1 */
border-radius: 8px; /* $border-radius-base 被覆盖为 8px */
}
.border {
border: 1px solid #ddd; /* $border-color 未被覆盖,使用 !default 值 */
}变量插值
scss
$direction: 'top';
$property: 'margin';
.border-#{$direction} {
border-#{$direction}: 1px solid #ddd;
}
.m-0 {
#{$property}: 0;
}
// 在选择器中使用
$namespace: 'ant';
.#{$namespace}-btn {
padding: 8px 16px;
}
.#{$namespace}-card {
border: 1px solid #eee;
}css
/* 编译后:#{} 插值被替换为变量的实际值 */
.border-top {
border-top: 1px solid #ddd;
}
.m-0 {
margin: 0;
}
.ant-btn {
padding: 8px 16px;
}
.ant-card {
border: 1px solid #eee;
}变量作用域
scss
$color: red;
.header {
$color: blue; // 局部变量
color: $color; // blue
}
.footer {
color: $color; // red(全局变量)
}
// 使用 !global 将局部变量提升为全局
.sidebar {
$width: 250px !global;
width: $width;
}
.main {
margin-left: $width; // 250px
}css
/* 编译后:局部变量只在定义的选择器内生效,!global 提升为全局 */
.header {
color: blue;
}
.footer {
color: red;
}
.sidebar {
width: 250px;
}
.main {
margin-left: 250px;
}嵌套
基本嵌套
scss
.nav {
background: #fff; // 导航栏背景色
height: 60px; // 导航栏高度
.nav-list {
display: flex; // 水平排列子元素
list-style: none; // 去掉列表圆点
margin: 0;
padding: 0;
}
.nav-item {
position: relative; // 为子元素绝对定位提供参考
&:hover { // & 代表父选择器 .nav-item,编译后为 .nav-item:hover
background: #f5f5f5;
}
&.active { // 编译后为 .nav-item.active
color: $primary;
&::after { // 伪元素:激活状态下底部的小蓝条指示器
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%); // 水平居中
width: 20px;
height: 2px;
background: $primary;
}
}
&:first-child { // 第一个子元素去掉左边距
margin-left: 0;
}
&:last-child { // 最后一个子元素去掉右边距
margin-right: 0;
}
}
.nav-link {
display: block;
padding: 0 $spacing-lg;
line-height: 60px; // 行高 = 容器高度,实现垂直居中
color: #333;
text-decoration: none; // 去掉下划线
transition: color 0.3s; // 颜色过渡动画
&:hover {
color: $primary;
}
}
// 子元素选择器:只匹配直接子代 .container
> .container {
display: flex;
align-items: center;
}
// 相邻兄弟选择器:.logo 后面紧跟的 .nav-list
.logo + .nav-list {
margin-left: $spacing-xl;
}
}css
/* 编译后:嵌套展开为后代选择器链,& 替换为父选择器 */
.nav {
background: #fff;
height: 60px;
}
.nav .nav-list {
display: flex;
list-style: none;
margin: 0;
padding: 0;
}
.nav .nav-item {
position: relative;
}
.nav .nav-item:hover {
background: #f5f5f5;
}
.nav .nav-item.active {
color: #1890ff;
}
.nav .nav-item.active::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 20px;
height: 2px;
background: #1890ff;
}
.nav .nav-item:first-child {
margin-left: 0;
}
.nav .nav-item:last-child {
margin-right: 0;
}
.nav .nav-link {
display: block;
padding: 0 24px;
line-height: 60px;
color: #333;
text-decoration: none;
transition: color 0.3s;
}
.nav .nav-link:hover {
color: #1890ff;
}
.nav > .container {
display: flex;
align-items: center;
}
.nav .logo + .nav-list {
margin-left: 32px;
}媒体查询嵌套
scss
.container {
width: 100%;
padding: 0 $spacing-md;
@media (min-width: $breakpoint-sm) {
max-width: 540px;
margin: 0 auto;
}
@media (min-width: $breakpoint-md) {
max-width: 720px;
}
@media (min-width: $breakpoint-lg) {
max-width: 960px;
}
@media (min-width: $breakpoint-xl) {
max-width: 1200px;
}
@media (min-width: $breakpoint-xxl) {
max-width: 1400px;
}
}
// 移动端优先(max-width)
.sidebar {
width: 100%;
display: none;
@media (min-width: $breakpoint-lg) {
width: 250px;
display: block;
}
}属性嵌套
scss
.box {
border: {
top: 1px solid #ddd;
right: 1px solid #ddd;
bottom: 1px solid #ddd;
left: 1px solid #ddd;
radius: 4px;
}
margin: {
top: 16px;
bottom: 16px;
}
background: {
color: #fff;
image: url('bg.png');
repeat: no-repeat;
position: center center;
size: cover;
}
font: {
family: $font-family-base;
size: $font-size-base;
weight: 400;
}
line-height: 1.5;
}Mixin(混合)
基本 Mixin
scss
// 定义 mixin:用 @mixin 关键字,名称自定义(建议用 kebab-case)
// 注意:mixin 名和变量名是独立的,不会冲突。如 $bordered(变量)和 @mixin bordered(混入)
@mixin bordered {
border: 1px solid #ddd;
border-radius: 4px;
}
// 为避免和变量名混淆,mixin 名建议加动词前缀,如 set-、make-、use-
@mixin set-box-shadow {
box-shadow: $box-shadow-sm;
}
@mixin text-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@mixin flex-center {
display: flex;
justify-content: center;
align-items: center;
}
@mixin flex-between {
display: flex;
justify-content: space-between;
align-items: center;
}
// 使用 mixin:用 @include 关键字引入
.card {
@include bordered; // 展开为 border + border-radius
@include set-box-shadow; // 展开为 box-shadow
padding: 16px;
.title {
@include text-ellipsis; // 单行省略号
font-size: 16px;
}
.footer {
@include flex-between; // 两端对齐布局
margin-top: 16px;
}
}css
/* 编译后:@mixin 定义不会输出到 CSS,@include 处展开为实际样式 */
.card {
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
padding: 16px;
}
.card .title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 16px;
}
.card .footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 16px;
}带参数的 Mixin
scss
// 带参数
@mixin size($width, $height: $width) {
width: $width;
height: $height;
}
@mixin square($size) {
@include size($size);
}
@mixin rectangle($width, $height: $width * 0.618) {
@include size($width, $height);
}
// 使用
.avatar {
@include square(48px);
}
.banner {
@include rectangle(100%, 200px);
}
.icon {
@include square(20px);
}css
/* 编译后:参数被替换为传入的实际值,默认值在未传参时生效 */
.avatar {
width: 48px;
height: 48px;
}
.banner {
width: 100%;
height: 200px;
}
.icon {
width: 20px;
height: 20px;
}默认参数
scss
@mixin padding($vertical: 8px, $horizontal: 16px) {
padding: $vertical $horizontal;
}
.btn-sm {
@include padding(4px, 8px);
}
.btn-md {
@include padding(); // 使用默认值
}
.btn-lg {
@include padding(12px, 24px);
}
// 多个默认参数
@mixin button($bg: $primary, $color: #fff, $radius: 4px) {
background: $bg;
color: $color;
border-radius: $radius;
border: none;
cursor: pointer;
}
.btn-primary {
@include button();
}
.btn-success {
@include button($success);
}
.btn-outline {
@include button(#fff, $primary, 20px);
border: 1px solid $primary;
}可变参数
scss
@mixin box-shadow($shadows...) {
box-shadow: $shadows;
}
.card {
@include box-shadow(0 2px 4px rgba(0,0,0,0.1));
}
.card-hover {
@include box-shadow(
0 2px 4px rgba(0,0,0,0.1),
0 4px 12px rgba(0,0,0,0.15)
);
}
// 渐变
@mixin gradient($direction, $colors...) {
background: linear-gradient($direction, $colors);
}
.hero {
@include gradient(to right, #667eea, #764ba2);
}@content(内容块)
scss
@mixin mobile {
@media (max-width: #{$breakpoint-md - 1px}) {
@content;
}
}
@mixin tablet {
@media (min-width: $breakpoint-md) and (max-width: #{$breakpoint-lg - 1px}) {
@content;
}
}
@mixin desktop {
@media (min-width: $breakpoint-lg) {
@content;
}
}
// 使用
.sidebar {
width: 250px;
@include mobile {
width: 100%;
display: none;
}
}
.card {
padding: 24px;
@include mobile {
padding: 16px;
}
}
// 带参数的 @content
@mixin theme($theme) {
@if $theme == 'light' {
--bg: #fff;
--text: #333;
--border: #ddd;
@content;
} @else if $theme == 'dark' {
--bg: #141414;
--text: #fff;
--border: #444;
@content;
}
}
.light-theme {
@include theme('light') {
background: var(--bg);
color: var(--text);
}
}css
/* 编译后:@content 块被插入到 mixin 内部,响应式 mixin 将内容包裹在媒体查询中 */
.sidebar {
width: 250px;
}
@media (max-width: 767px) {
.sidebar {
width: 100%;
display: none;
}
}
.card {
padding: 24px;
}
@media (max-width: 767px) {
.card {
padding: 16px;
}
}
.light-theme {
--bg: #fff;
--text: #333;
--border: #ddd;
background: var(--bg);
color: var(--text);
}占位符和继承
占位符选择器
大白话解释: 占位符 % 就像一个"模板",定义了样式但不会输出到最终 CSS 中。只有被 @extend 引用时才会生效,比 @mixin 更高效(合并选择器而非复制代码)。
什么时候用 @extend vs @mixin?
@extend:样式相同、不需要参数时,合并选择器减少代码体积@mixin:需要传参定制时,灵活但会复制代码到每个使用处
scss
// % 占位符不会输出到 CSS(没有被引用时完全消失)
%center {
display: flex;
justify-content: center;
align-items: center;
}
%text-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
%card-base {
border: 1px solid #ddd;
border-radius: 4px;
padding: 16px;
}
// 使用 @extend 继承:合并选择器,减少重复代码
.box {
@extend %center; // 编译后:.box 和其他继承者共享同一组样式
width: 100px;
height: 100px;
}
.title {
@extend %text-ellipsis; // 编译后:选择器被合并到 %text-ellipsis 定义处
font-size: 16px;
}
.card {
@extend %card-base; // 占位符被引用后才输出
box-shadow: $box-shadow-sm;
}css
/* 编译后:占位符 % 不输出,@extend 会合并选择器(比 mixin 更高效) */
.box {
display: flex;
justify-content: center;
align-items: center;
width: 100px;
height: 100px;
}
.title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 16px;
}
.card {
border: 1px solid #ddd;
border-radius: 4px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
}继承选择器
scss
.message {
padding: 12px 16px;
border: 1px solid #ddd;
border-radius: 4px;
margin-bottom: 16px;
}
.success {
@extend .message;
border-color: $success;
color: $success;
background: lighten($success, 40%);
}
.error {
@extend .message;
border-color: $danger;
color: $danger;
background: lighten($danger, 40%);
}
.warning {
@extend .message;
border-color: $warning;
color: $warning;
background: lighten($warning, 40%);
}css
/* 编译后:@extend 将子类选择器合并到父类规则中 */
.message, .success, .error, .warning {
padding: 12px 16px;
border: 1px solid #ddd;
border-radius: 4px;
margin-bottom: 16px;
}
.success {
border-color: #52c41a;
color: #52c41a;
background: #f6ffed;
}
.error {
border-color: #ff4d4f;
color: #ff4d4f;
background: #fff2f0;
}
.warning {
border-color: #faad14;
color: #faad14;
background: #fffbe6;
}@extend vs @mixin
scss
// @extend:合并选择器,减少代码重复(适合固定样式复用)
%button-base {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-primary {
@extend %button-base; // 编译后:.btn-primary, .btn-secondary 共享基础样式
background: $primary;
color: #fff;
}
.btn-secondary {
@extend %button-base;
background: #666;
color: #fff;
}
// 输出:.btn-primary, .btn-secondary { padding: 8px 16px; ... }
// @mixin:复制代码到每个使用处(适合需要参数定制的场景)
@mixin button($bg) {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
background: $bg; // 参数使每个按钮颜色不同,所以不能用 @extend
color: #fff;
}
.btn-primary { @include button($primary); }
.btn-secondary { @include button(#666); }
// 输出:每个选择器都有完整的样式(代码重复,但参数不同)css
/* 编译后对比:
@extend 合并选择器,减少重复:
%button-base, .btn-primary, .btn-secondary {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-primary { background: #1890ff; color: #fff; }
.btn-secondary { background: #666; color: #fff; }
@mixin 复制代码到每个使用处(适合带参数):
.btn-primary {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
background: #1890ff;
color: #fff;
}
.btn-secondary {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
background: #666;
color: #fff;
}
*/
@extend注意事项:
- 不要跨媒体查询继承:在
@media内使用@extend引用外部占位符会报错。- 选择器合并可能产生意外组合:多个
@extend同一个占位符时,编译器会生成所有选择器的组合,CSS 文件可能变大。- 不要嵌套
@extend:@extend只在编译时展开,运行时没有继承关系,嵌套调用不会产生级联效果。- 优先用
%占位符而非类选择器:用@extend .message继承普通类会污染.message的输出,%message不会。
函数
颜色函数
scss
$base: #1890ff;
// 亮度调整
.lighter { color: lighten($base, 20%); }
.darker { color: darken($base, 20%); }
// 饱和度调整
.saturated { color: saturate($base, 20%); }
.desaturated { color: desaturate($base, 20%); }
// 色相调整
.hue-rotate { color: adjust-hue($base, 60deg); }
// 混合颜色
.mixed { color: mix($base, #ff0000, 50%); }
// 透明度
.fade { color: rgba($base, 0.5); }
.fade-in { color: opacify($base, 0.2); }
.fade-out { color: transparentize($base, 0.2); }
// 灰度
.grayscale { color: grayscale($base); }
// 对比度
.contrast { color: contrast($base, #000, #fff); }
// 互补色
.complement { color: complement($base); }数学函数
scss
.ceil { width: ceil(1.2px); } // 2px
.floor { width: floor(1.8px); } // 1px
.round { width: round(1.5px); } // 2px
.abs { width: abs(-10px); } // 10px
.max { width: max(10px, 20px); } // 20px
.min { width: min(10px, 20px); } // 10px
.percentage { width: percentage(0.5); } // 50%
.random { width: random(100) * 1px; } // 随机值字符串函数
scss
.to-upper { content: to-upper-case('hello'); } // HELLO
.to-lower { content: to-lower-case('HELLO'); } // hello
.str-length { content: str-length('hello'); } // 5
.slice { content: str-slice('hello', 1, 3); } // hel
.insert { content: str-insert('hello', ' world', 6); } // hello world
.index { content: str-index('hello', 'l'); } // 3自定义函数
scss
// 自定义函数
@function calc-rem($px, $base: 16px) {
@return ($px / $base) * 1rem;
}
@function calc-percent($part, $whole) {
@return percentage($part / $whole);
}
@function strip-unit($value) {
@return $value / ($value * 0 + 1);
}
// 使用
.title {
font-size: calc-rem(24px); // 1.5rem
}
.col-6 {
width: calc-percent(6, 12); // 50%
}
$spacing: 16px;
$spacing-value: strip-unit($spacing); // 16css
/* 编译后:@function 返回值在编译期计算 */
.title {
font-size: 1.5rem; /* calc-rem(24px) */
}
.col-6 {
width: 50%; /* calc-percent(6, 12) */
}控制指令
@if / @else if / @else
scss
@mixin theme($theme) {
@if $theme == 'light' {
background: #fff;
color: #333;
border-color: #ddd;
} @else if $theme == 'dark' {
background: #141414;
color: #fff;
border-color: #444;
} @else {
background: #f5f5f5;
color: #666;
border-color: #eee;
}
}
.light { @include theme('light'); }
.dark { @include theme('dark'); }
.gray { @include theme('gray'); }
// 条件判断
@mixin visibility($visible) {
@if $visible {
display: block;
opacity: 1;
} @else {
display: none;
opacity: 0;
}
}css
/* 编译后:@if/@else 只输出匹配条件的分支 */
.light {
background: #fff;
color: #333;
border-color: #ddd;
}
.dark {
background: #141414;
color: #fff;
border-color: #444;
}
.gray {
background: #f5f5f5;
color: #666;
border-color: #eee;
}@for
scss
// from through(包含结束值)
@for $i from 1 through 5 {
.mt-#{$i} { margin-top: $i * 8px; }
.mb-#{$i} { margin-bottom: $i * 8px; }
.ml-#{$i} { margin-left: $i * 8px; }
.mr-#{$i} { margin-right: $i * 8px; }
}
// from to(不包含结束值)
@for $i from 1 to 13 {
.col-#{$i} {
width: percentage($i / 12);
float: left;
box-sizing: border-box;
padding: 0 15px;
}
}css
/* 编译后:循环展开为所有生成的类(这里展示部分) */
.mt-1 { margin-top: 8px; }
.mb-1 { margin-bottom: 8px; }
.ml-1 { margin-left: 8px; }
.mr-1 { margin-right: 8px; }
.mt-2 { margin-top: 16px; }
.mb-2 { margin-bottom: 16px; }
.ml-2 { margin-left: 16px; }
.mr-2 { margin-right: 16px; }
/* ... mt-3 到 mt-5 省略 ... */
.col-1 {
width: 8.33333%;
float: left;
box-sizing: border-box;
padding: 0 15px;
}
.col-2 {
width: 16.66667%;
float: left;
box-sizing: border-box;
padding: 0 15px;
}
/* ... col-3 到 col-11 省略 ... */
.col-12 {
width: 100%;
float: left;
box-sizing: border-box;
padding: 0 15px;
}@each
scss
// 遍历 map
$theme-colors: (
primary: #1890ff,
success: #52c41a,
danger: #ff4d4f,
warning: #faad14,
info: #1890ff
);
@each $name, $color in $theme-colors {
.btn-#{$name} {
background: $color;
color: #fff;
}
}
// 遍历 map
$sizes: (
sm: 12px,
md: 14px,
lg: 16px,
xl: 20px
);
@each $name, $size in $sizes {
.text-#{$name} {
font-size: $size;
}
}
// 多个值遍历
$icons: (
home: '\e900',
user: '\e901',
settings: '\e902',
search: '\e903'
);
@each $name, $code in $icons {
.icon-#{$name}::before {
content: $code;
}
}css
/* 编译后:@each 遍历 map 生成所有类 */
.btn-primary {
background: #1890ff;
color: #fff;
}
.btn-success {
background: #52c41a;
color: #fff;
}
.btn-danger {
background: #ff4d4f;
color: #fff;
}
.btn-warning {
background: #faad14;
color: #fff;
}
.btn-info {
background: #1890ff;
color: #fff;
}
.text-sm {
font-size: 12px;
}
.text-md {
font-size: 14px;
}
.text-lg {
font-size: 16px;
}
.text-xl {
font-size: 20px;
}
.icon-home::before {
content: '\e900';
}
.icon-user::before {
content: '\e901';
}
.icon-settings::before {
content: '\e902';
}
.icon-search::before {
content: '\e903';
}@while
scss
$i: 1;
@while $i <= 5 {
.col-#{$i} {
width: percentage($i / 12);
}
$i: $i + 1;
}css
/* 编译后:@while 循环展开为所有生成的类 */
.col-1 {
width: 8.33333%;
}
.col-2 {
width: 16.66667%;
}
.col-3 {
width: 25%;
}
.col-4 {
width: 33.33333%;
}
.col-5 {
width: 41.66667%;
}Maps
基本用法
scss
// 定义 map:类似 JS 中的对象,key-value 键值对
$breakpoints: (
'sm': 576px,
'md': 768px,
'lg': 1024px,
'xl': 1280px,
'xxl': 1600px
);
$colors: (
'primary': #1890ff,
'success': #52c41a,
'danger': #ff4d4f,
'warning': #faad14,
'info': #1890ff
);
$spacing: (
'xs': 4px,
'sm': 8px,
'md': 16px,
'lg': 24px,
'xl': 32px,
'xxl': 48px
);
// map 函数:按 key 获取值(类似 JS 的 obj[key])
$primary: map-get($colors, 'primary'); // #1890ff
$breakpoint-lg: map-get($breakpoints, 'lg'); // 1024px
// 检查 key 是否存在(避免报错)
@if map-has-key($colors, 'primary') {
// 存在才执行
}
// 合并 map:把两个 map 合成一个
$more-colors: ('pink': #eb2f96, 'cyan': #13c2c2');
$all-colors: map-merge($colors, $more-colors); // 包含所有颜色
// 获取所有 keys 和 values(用于遍历)
$color-names: map-keys($colors); // ('primary', 'success', ...)
$color-values: map-values($colors); // (#1890ff, #52c41a, ...)生成工具类
scss
// 生成颜色工具类
@each $name, $color in $colors {
.text-#{$name} {
color: $color;
}
.bg-#{$name} {
background-color: $color;
}
.bg-#{$name}-light {
background-color: rgba($color, 0.1);
}
.border-#{$name} {
border-color: $color;
}
}
// 生成间距工具类
@each $name, $value in $spacing {
.m-#{$name} { margin: $value; }
.mt-#{$name} { margin-top: $value; }
.mr-#{$name} { margin-right: $value; }
.mb-#{$name} { margin-bottom: $value; }
.ml-#{$name} { margin-left: $value; }
.mx-#{$name} { margin-left: $value; margin-right: $value; }
.my-#{$name} { margin-top: $value; margin-bottom: $value; }
.p-#{$name} { padding: $value; }
.pt-#{$name} { padding-top: $value; }
.pr-#{$name} { padding-right: $value; }
.pb-#{$name} { padding-bottom: $value; }
.pl-#{$name} { padding-left: $value; }
.px-#{$name} { padding-left: $value; padding-right: $value; }
.py-#{$name} { padding-top: $value; padding-bottom: $value; }
}
// 生成响应式工具类
@each $name, $breakpoint in $breakpoints {
@media (min-width: $breakpoint) {
.d-#{$name}-none { display: none !important; }
.d-#{$name}-block { display: block !important; }
.d-#{$name}-flex { display: flex !important; }
.d-#{$name}-inline { display: inline !important; }
.d-#{$name}-inline-block { display: inline-block !important; }
}
}css
/* 编译后:map 遍历 + 插值生成大量工具类(这里展示部分) */
.text-primary {
color: #1890ff;
}
.bg-primary {
background-color: #1890ff;
}
.bg-primary-light {
background-color: rgba(24, 144, 255, 0.1);
}
.border-primary {
border-color: #1890ff;
}
/* success、danger、warning、info 同理省略 */
.m-xs { margin: 4px; }
.mt-xs { margin-top: 4px; }
.mr-xs { margin-right: 4px; }
.mb-xs { margin-bottom: 4px; }
.ml-xs { margin-left: 4px; }
.mx-xs { margin-left: 4px; margin-right: 4px; }
.my-xs { margin-top: 4px; margin-bottom: 4px; }
.p-xs { padding: 4px; }
/* sm、md、lg、xl、xxl 同理省略 */
@media (min-width: 576px) {
.d-sm-none { display: none !important; }
.d-sm-block { display: block !important; }
.d-sm-flex { display: flex !important; }
.d-sm-inline { display: inline !important; }
.d-sm-inline-block { display: inline-block !important; }
}
/* md、lg、xl、xxl 同理省略 */Partials 和导入
文件组织
src/
styles/
_variables.scss // 变量
_functions.scss // 函数
_mixins.scss // mixin
_placeholders.scss // 占位符
_reset.scss // 重置样式
_utilities.scss // 工具类
_typography.scss // 排版
components/
_button.scss
_card.scss
_form.scss
_table.scss
_modal.scss
themes/
_light.scss
_dark.scss
main.scss // 主入口@use 和 @forward(推荐)
大白话解释:
@use:引入模块并使用它的变量/mixin/函数,会自动加上命名空间避免冲突(类似 JS 的import)@forward:把一个模块"转发"出去,自己不使用,只是作为统一出口(类似 JS 的export)
scss
// _variables.scss(定义变量的模块)
$primary: #1890ff;
$success: #52c41a;
$danger: #ff4d4f;
// _mixins.scss(定义 mixin 的模块,需要引用变量时用 @use)
@use 'variables' as vars; // 引入变量模块,命名为 vars
@mixin flex-center {
display: flex;
justify-content: center;
align-items: center;
}
@mixin button($bg: vars.$primary) { // 通过 命名空间.变量名 访问
background: $bg;
color: #fff;
padding: 8px 16px;
border: none;
border-radius: 4px;
}
// main.scss(入口文件,用 @use 引入需要的模块)
@use 'variables' as vars; // 引入变量模块
@use 'mixins' as mix; // 引入 mixin 模块
.container {
@include mix.flex-center; // 命名空间.mixin名
color: vars.$primary; // 命名空间.变量名
}
.btn {
@include mix.button();
}@forward(模块导出)
@forward 的作用: 当你有一个文件夹组织了很多模块,不想让使用者逐个 @use,就用 @forward 创建一个"统一出口"。
scss
// _index.scss(统一出口:转发所有子模块,自身不使用任何变量)
@forward 'variables'; // 转发变量模块
@forward 'mixins'; // 转发 mixin 模块
@forward 'functions'; // 转发函数模块
// 使用方:只需要 @use 一次,就能访问所有子模块的内容
@use 'styles' as styles; // styles 目录下的 _index.scss
.box {
color: styles.$primary; // 通过 命名空间.变量名 访问 variables 中的变量
@include styles.flex-center; // 通过 命名空间.mixin名 访问 mixins 中的 mixin
}
@usevs@forward区别:
@use:自己要用,直接引入模块并访问@forward:自己不用,只是把模块"转发"给外部使用者,作为统一入口
@import(旧语法,不推荐)
警告: Sass 官方已废弃
@import,将在未来版本移除。请使用@use和@forward替代。
scss
// 旧语法(已废弃,新项目不要使用)
@import 'variables';
@import 'mixins';
@import 'reset';
// 问题:
// 1. 全局作用域,容易命名冲突(两个文件都定义 $primary 会互相覆盖)
// 2. 无法控制导出(所有变量/mixin 都暴露给全局)
// 3. 可能重复导入(同一个文件被多处 @import 会重复输出)
// 4. 无法按需加载(只能全量引入)实际应用场景
主题系统
scss
// _theme-variables.scss
$themes: (
light: (
bg: #ffffff,
bg-secondary: #f5f5f5,
text: #333333,
text-secondary: #666666,
border: #dddddd,
primary: #1890ff,
shadow: rgba(0, 0, 0, 0.09)
),
dark: (
bg: #141414,
bg-secondary: #1f1f1f,
text: #ffffff,
text-secondary: #aaaaaa,
border: #444444,
primary: #177ddc,
shadow: rgba(0, 0, 0, 0.3)
)
);
// 生成主题变量
@each $theme, $colors in $themes {
[data-theme="#{$theme}"] {
@each $key, $value in $colors {
--#{$key}: #{$value};
}
}
}
// 使用 CSS 变量
body {
background: var(--bg);
color: var(--text);
}
.card {
background: var(--bg);
border: 1px solid var(--border);
box-shadow: 0 2px 8px var(--shadow);
}css
/* 编译后:嵌套 each 循环生成 CSS 变量 */
[data-theme="light"] {
--bg: #ffffff;
--bg-secondary: #f5f5f5;
--text: #333333;
--text-secondary: #666666;
--border: #dddddd;
--primary: #1890ff;
--shadow: rgba(0, 0, 0, 0.09);
}
[data-theme="dark"] {
--bg: #141414;
--bg-secondary: #1f1f1f;
--text: #ffffff;
--text-secondary: #aaaaaa;
--border: #444444;
--primary: #177ddc;
--shadow: rgba(0, 0, 0, 0.3);
}
body {
background: var(--bg);
color: var(--text);
}
.card {
background: var(--bg);
border: 1px solid var(--border);
box-shadow: 0 2px 8px var(--shadow);
}响应式工具类
scss
// 响应式工具 mixin
@mixin respond-to($breakpoint) {
@if map-has-key($breakpoints, $breakpoint) {
@media (min-width: map-get($breakpoints, $breakpoint)) {
@content;
}
} @else {
@warn "Unknown breakpoint: #{$breakpoint}";
}
}
// 使用
.sidebar {
display: none;
@include respond-to('lg') {
display: block;
width: 250px;
}
}
.content {
padding: 16px;
@include respond-to('md') {
padding: 24px;
}
@include respond-to('lg') {
padding: 32px;
}
}css
/* 编译后:respond-to mixin 根据断点名查找 map 值,生成媒体查询 */
.sidebar {
display: none;
}
@media (min-width: 1024px) {
.sidebar {
display: block;
width: 250px;
}
}
.content {
padding: 16px;
}
@media (min-width: 768px) {
.content {
padding: 24px;
}
}
@media (min-width: 1024px) {
.content {
padding: 32px;
}
}Vue 中使用 SCSS
基础配置
vue
<template>
<div class="container">
<button class="btn btn-primary">按钮</button>
<div class="card">
<h3 class="card__title">标题</h3>
<p class="card__desc">描述</p>
</div>
</div>
</template>
<style lang="scss" scoped>
@use '@/styles/variables' as vars;
@use '@/styles/mixins' as mix;
.container {
padding: vars.$spacing-lg;
}
.btn {
padding: vars.$spacing-sm vars.$spacing-md;
border: none;
border-radius: vars.$border-radius-base;
cursor: pointer;
&-primary {
background: vars.$primary;
color: #fff;
&:hover {
background: darken(vars.$primary, 10%);
}
}
}
.card {
border: 1px solid #eee;
border-radius: vars.$border-radius-lg;
padding: vars.$spacing-lg;
&__title {
font-size: vars.$font-size-lg;
margin-bottom: vars.$spacing-sm;
}
&__desc {
color: #666;
font-size: vars.$font-size-base;
}
}
</style>全局变量注入
js
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
css: {
preprocessorOptions: {
scss: {
// 全局注入(Sass 新语法)
additionalData: `@use "@/styles/variables" as *;`,
// 旧语法
// additionalData: `@import "@/styles/variables";`
}
}
}
})CSS Modules
vue
<template>
<div :class="$style.container">
<button :class="[$style.btn, $style.btnPrimary]">按钮</button>
</div>
</template>
<style lang="scss" module>
$primary: #1890ff;
.container {
padding: 24px;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btnPrimary {
background: $primary;
color: #fff;
&:hover {
background: darken($primary, 10%);
}
}
</style>Less vs Sass 对比
| 特性 | Less | Sass/SCSS |
|---|---|---|
| 变量符号 | @var | $var |
| 嵌套 | ✅ | ✅ |
| Mixin | .mixin() | @mixin + @include |
| 继承 | 不支持 | @extend |
| 函数 | 有限 | 丰富(可自定义) |
| 控制指令 | when + each | @if、@for、@each、@while |
| Maps | 不支持 | 支持 |
| 模块系统 | @import | @use + @forward |
| 学习曲线 | 较低 | 较高 |
| 社区生态 | 较小 | 更大 |
| 推荐场景 | 简单项目 | 复杂项目 |