SCSS 语法
SCSS 的强大之处在于它将编程思想引入了 CSS,让样式表具备了变量、逻辑和复用能力。本文将结合大量代码实例,全面解析 SCSS 的核心语法。
一、CSS 功能拓展
1. 嵌套规则与父选择器 &
SCSS 允许我们像写 HTML 结构一样嵌套 CSS 规则,这让代码层级关系一目了然。& 代表父选择器,常用于伪类(如 :hover)或需要组合选择器来创建新选择器的场景。
a {
font-weight: bold;
text-decoration: none;
// 编译为 a:hover
&:hover {
text-decoration: underline;
}
// 编译为 body.firefox a
body.firefox & {
font-weight: normal;
}
}
// & 也可以用于拼接选择器
#main {
color: black;
// 编译为 #main-sidebar
&-sidebar {
border: 1px solid;
}
}a {
font-weight: bold;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
body.firefox a {
font-weight: normal;
}
#main {
color: black;
}
#main-sidebar {
border: 1px solid;
}2. 属性嵌套
对于 font-family, font-size 这类有共同命名空间的属性,SCSS 允许将它们嵌套在一起,避免重复输入。
.funky {
font: {
family: fantasy;
size: 30em;
weight: bold;
}
}
.funky {
// 命名空间也可以包含自己的属性值
font: 20px/24px {
family: fantasy;
weight: bold;
}
}.funky {
font-family: fantasy;
font-size: 30em;
font-weight: bold;
}
.funky {
font: 20px/24px;
font-family: fantasy;
font-weight: bold;
}二、SassScript 核心:变量、数据与运算
1. 变量
变量以 $ 开头,是 SCSS 最基础也最常用的功能。在嵌套规则内定义的变量是局部变量,使用 !global 标志可将其提升为全局变量。!default 则用于设置默认值,仅在变量未被赋值时生效。
// !global 示例
#main {
$width: 5em !global;
width: $width;
}
#sidebar {
width: $width; // 可以访问到 #main 内部定义的全局变量
}
// !default 示例
$content: 'First content';
$content: 'Second content?' !default; // 因为 $content 已有值,所以此行被忽略
$new_content: 'First time reference' !default; // $new_content 无值,所以被赋值
#main {
content: $content;
new-content: $new_content;
}
// null 值会被 !default 视为未赋值
$content: null;
$content: 'Non-null content' !default; // 此行生效#main {
width: 5em;
}
#sidebar {
width: 5em;
}
#main {
content: 'First content';
new-content: 'First time reference';
}
#main {
content: 'Non-null content';
}2. 数据类型
非常抱歉,这是我的严重失误!在之前为你整理博客时,我确实把“数据类型”这一核心章节给遗漏了。
SCSS 的数据类型(如数字、字符串、颜色、布尔值、空值、数组、Maps)是编写高级 SassScript 的基础。我已经为你补全了这部分内容,并严格遵循了 VitePress 的 code-group 左右对比格式。
你可以直接将这部分插入到博客的**“二、SassScript 核心:变量、数据与运算”**章节中(建议放在“变量”之后,“运算”之前):
2. 数据类型
SCSS 支持多种数据类型,它们是实现逻辑控制和复杂计算的基础。主要包括:数字(Numbers)、字符串(Strings)、颜色(Colors)、布尔值(Booleans)、空值(Null)、数组(Lists)和 Maps。
// 1. 数字 (Numbers):支持整数、小数及单位
$len: 10;
$wid: 20.5;
// 2. 布尔值 (Booleans):true 或 false
$has-border: true;
// 3. 字符串 (Strings):带引号或不带引号
$heading: 'Welcome';
$font-family: 'Courier New', Courier, monospace;
// 4. 颜色 (Colors):支持 CSS 原生颜色格式
$primary: rgb(214, 121, 45);
$translucent: rgba(210, 122, 54, 0.5);
// 5. 空值 (Null):表示未知或无值
$value: null;
// 6. 数组 (Lists):空格或逗号分隔的值序列
$list-space: 10px 20px 30px;
$list-comma: Helvetica, Arial, sans-serif;
// 7. Maps:键值对集合,常用于主题配置
$theme-map: (
'primary': #007bff,
'text': #333,
'spacing': 8px
);
// 使用示例
.box {
width: $len + px;
font-family: $font-family;
color: $primary;
background: $translucent;
// 使用 map-get() 获取 Map 中的值
border-color: map-get($theme-map, 'primary');
}.box {
width: 10px;
font-family: 'Courier New', Courier, monospace;
color: #d6792d;
background: rgba(210, 122, 54, 0.5);
border-color: #007bff;
}3. 运算
SCSS 支持数字、颜色、字符串的运算。除法 / 只有在值是变量、被括号包裹或作为算数表达式一部分时才会被执行。插值 #{} 可用于强制将变量作为普通值输出。
$width: 1000px;
$font-size: 12px;
$line-height: 30px;
$translucent-red: rgba(255, 0, 0, 0.5);
$value: null;
p {
font: 10px/8px; // 普通 CSS,/ 作为分隔符
width: $width/2; // 使用变量,执行除法 -> 500px
padding: round(1.5) / 2; // 使用函数,执行除法 -> 1
height: (500px/2); // 使用括号,执行除法 -> 250px
margin-left: 5px + 8px/2px; // 算数表达式,执行除法 -> 9px
// 使用插值强制将变量作为普通值输出,避免运算
font: #{$font-size}/#{$line-height};
// 颜色运算 (alpha 通道需相等)
color: opacify($translucent-red, 0.3); // -> rgba(255, 0, 0, 0.8)
background-color: transparentize($translucent-red, 0.25); // -> rgba(255, 0, 0, 0.25)
}
/**
* 如果有引号字符串(位于 + 左侧)连接无引号字符串,运算结果是有引号的,
* 相反,无引号字符串(位于 + 左侧)连接有引号字符串,运算结果则没有引号。
*/
p:before {
content: 'Foo ' + Bar; // 字符串拼接 -> 'Foo Bar'
font-family: sans- + 'serif'; // -> sans-serif
margin: 3px + 4px auto; // 运算表达式与其他值连用时,用空格做连接符 -> 7px auto
content: 'I ate #{5 + 10} pies!'; // 插值表达式 -> 'I ate 15 pies!'
content: 'I ate #{$value} pies!'; // 空的值被视作插入了空字符串 -> 'I ate pies!'
}
// 插值语句可以在选择器或属性名中使用变量
$name: foo;
$attr: border;
p.#{$name} {
#{$attr}-color: blue;
}@charset "UTF-8";
p {
font: 10px/8px;
width: 500px;
padding: 1;
height: 250px;
margin-left: 9px;
font: 12px/30px;
color: rgba(255, 0, 0, 0.8);
background-color: rgba(255, 0, 0, 0.25);
}
/**
* 如果有引号字符串(位于 + 左侧)连接无引号字符串,运算结果是有引号的,
* 相反,无引号字符串(位于 + 左侧)连接有引号字符串,运算结果则没有引号。
*/
p:before {
content: 'Foo Bar';
font-family: sans-serif;
margin: 7px auto;
content: 'I ate 15 pies!';
content: 'I ate pies!';
}
p.foo {
border-color: blue;
}三、核心指令
1. 导入 @use 与媒体查询 @media
@use 带命名空间
@use 'common.scss';
@use 'var.scss' as b;
$_n: 6; // 私有变量
.foo {
color: common.$color;
}@media 支持嵌套在 CSS 规则内部,编译时会自动“冒泡”到最外层并合并选择器。
$media: screen;
$feature: -webkit-min-device-pixel-ratio;
$value: 1.5;
.sidebar {
width: 300px;
@media screen and (orientation: landscape) {
width: 500px;
}
}
// @media 甚至可以使用变量和插值
@media #{$media} and ($feature: $value) {
width: 600px;
}
// @media 的 queries 允许互相嵌套使用,编译时,Sass 自动添加 and
@media screen {
.sidebar {
@media (orientation: landscape) {
width: 500px;
}
}
}.sidebar {
width: 300px;
}
@media screen and (orientation: landscape) {
.sidebar {
width: 500px;
}
}
@media screen and (-webkit-min-device-pixel-ratio: 1.5) {
.sidebar {
width: 600px;
}
}
@media screen and (orientation: landscape) {
.sidebar {
width: 500px;
}
}2. 继承 @extend
在设计网页的时候常常遇到这种情况:一个元素使用的样式与另一个元素完全相同,但又添加了额外的样式。通常会在 HTML 中给元素定义两个 class,一个通用样式,一个特殊样式。假设现在要设计一个普通错误样式与一个严重错误样式,一般会这样写:
<style>
.error {
border: 1px #f00;
background-color: #fdd;
}
.seriousError {
border-width: 3px;
}
</style>
<div class="error seriousError">Oh no! You've been hacked!</div>麻烦的是,这样做必须时刻记住使用 .seriousError 时需要参考 .error 的样式,带来了很多不变:智能比如加重维护负担,导致 bug,或者给 HTML 添加无语意的样式。使用 @extend 可以避免上述情况,告诉 Sass 将一个选择器下的所有样式继承给另一个选择器。
.error {
border: 1px #f00;
background-color: #fdd;
}
.seriousError {
@extend .error;
border-width: 3px;
}上面代码的意思是将 .error 下的所有样式继承给 .seriousError,border-width: 3px; 是单独给 .seriousError 设定特殊样式,这样,使用 .seriousError 的地方可以不再使用 .error。
其他使用到 .error 的样式也会同样继承给 .seriousError,例如,另一个样式 .error.intrusion 使用了 hacked.png 做背景,<div class="seriousError intrusion"> 也同样会使用 hacked.png 背景。
.error.intrusion {
background-image: url('/image/hacked.png');
}@extend 可以让一个选择器继承另一个选择器的所有样式。占位符选择器 % 不会单独编译到 CSS 中,非常适合定义仅供继承的样式。
// 基础样式
.error {
border: 1px #f00;
background-color: #fdd;
}
.error.intrusion {
background-image: url('/image/hacked.png');
}
// 继承样式
.seriousError {
@extend .error;
border-width: 3px;
}
// 合并选择器列
#admin .tabbar a {
font-weight: bold;
}
#demo .overview .fakelink {
@extend a;
}
// 包含相同的选择器,相同部分将会合并在一起,其他部分交替输出
#admin .overview .fakelink {
@extend a;
}
// %占位符选择器
#context %extreme {
color: blue;
font-weight: bold;
font-size: 2em;
}
.notice {
@extend %extreme;
}.error,
.seriousError {
border: 1px #f00;
background-color: #fdd;
}
.error.intrusion,
.intrusion.seriousError {
background-image: url('/image/hacked.png');
}
.seriousError {
border-width: 3px;
}
#admin .tabbar a,
#admin .tabbar .overview .fakelink,
#admin .overview .tabbar .fakelink,
#demo .overview #admin .tabbar .fakelink {
font-weight: bold;
}
#context .notice {
color: blue;
font-weight: bold;
font-size: 2em;
}如果 @extend 失败会收到错误提示,比如,这样写 a.important {@extend .notice},当没有 .notice 选择器时,将会报错,只有 h1.notice 包含 .notice 时也会报错,因为 h1 与 a 冲突,会生成新的选择器。
如果要求 @extend 不生成新选择器,可以通过 !optional 声明达到这个目的
a.important {
@extend .notice !optional;
}3. 控制指令
SCSS 提供了 @if, @for, @each, @while 等指令,为 CSS 带来了逻辑控制能力。
当 @if 的表达式返回值不是 false 或者 null 时,条件成立,输出 {} 内的代码。@if 声明后面可以跟多个 @else if 声明,或者一个 @else 声明。如果 @if 声明失败,Sass 将逐条执行 @else if 声明,如果全部失败,最后执行 @else 声明。
@for 指令可以在限制的范围内重复输出格式,每次按要求(变量的值)对输出结果做出变动。这个指令包含两种格式:@for $var from <start> through <end>,或者 @for $var from <start> to <end>,区别在于 through 与 to 的含义:当使用 through 时,条件范围包含 <start> 与 <end> 的值,而使用 to 时条件范围只包含 <start> 的值不包含 <end> 的值。另外,$var 可以是任何变量,比如 $i;<start> 和 <end> 必须是整数值。
@each 指令的格式是 $var in <list>, $var 可以是任何变量名,比如 $length 或者 $name,而 <list> 是一连串的值,也就是值列表。
@while 指令重复输出格式直到表达式返回结果为 false。这样可以实现比 @for 更复杂的循环,只是很少会用到。
$type: monster;
p {
// @if 条件判断
@if 1 + 1 == 2 {
border: 1px solid;
}
@if 5 < 3 {
border: 2px dotted;
}
@if null {
border: 3px double;
}
@if $type == ocean {
color: blue;
} @else if $type == matador {
color: red;
} @else if $type == monster {
color: green;
} @else {
color: black;
}
}
// @for 循环 (through 包含结束值,to 不包含)
@for $i from 1 through 3 {
.item-#{$i} {
width: 2em * $i;
}
}
// @each 遍历列表
@each $animal in puma, sea-slug {
.#{$animal}-icon {
background-image: url('/images/#{$animal}.png');
}
}
// @each 遍历多维列表
@each $animal, $color in (puma, black), (sea-slug, blue) {
.#{$animal}-icon {
border: 2px solid $color;
}
}
// @while 循环
$i: 6;
@while $i > 0 {
.item-#{$i} {
width: 2em * $i;
}
$i: $i - 2;
}p {
border: 1px solid;
color: green;
}
.item-1 {
width: 2em;
}
.item-2 {
width: 4em;
}
.item-3 {
width: 6em;
}
.puma-icon {
background-image: url('/images/puma.png');
}
.sea-slug-icon {
background-image: url('/images/sea-slug.png');
}
.puma-icon {
border: 2px solid black;
}
.sea-slug-icon {
border: 2px solid blue;
}
.item-6 {
width: 12em;
}
.item-4 {
width: 8em;
}
.item-2 {
width: 4em;
}四、混合宏与函数
1. 混合宏 @mixin 与 @include
混合宏是封装可复用代码块的强大工具。支持参数与默认值、可变参数 ... 以及通过 @content 传入代码块。
// 基础
@mixin large-text {
font: {
family: Arial;
size: 20px;
weight: bold;
}
color: #ff0000;
}
.page-title {
@include large-text;
padding: 4px;
margin-top: 10px;
}
// 在最外层引用混合样式,不会直接定义属性
@mixin silly-links {
a {
color: blue;
background-color: red;
}
}
@include silly-links;
// 带参数和默认值
@mixin sexy-border($color, $width: 1in) {
border: {
color: $color;
width: $width;
style: dashed;
}
}
p {
@include sexy-border(blue);
}
// 可变参数
@mixin colors($text, $background, $border) {
color: $text;
background-color: $background;
border-color: $border;
}
$values: #ff0000, #00ff00, #0000ff;
.primary {
@include colors($values...);
}
// 引入混合样式
@mixin apply-to-ie6-only {
* html {
@content;
}
}
@include apply-to-ie6-only {
#logo {
background-image: url(/logo.gif);
}
}.page-title {
font-family: Arial;
font-size: 20px;
font-weight: bold;
color: #ff0000;
padding: 4px;
margin-top: 10px;
}
a {
color: blue;
background-color: red;
}
p {
border-color: blue;
border-width: 1in;
border-style: dashed;
}
.primary {
color: #ff0000;
background-color: #00ff00;
border-color: #0000ff;
}
* html #logo {
background-image: url(/logo.gif);
}2. 函数 @function
当需要进行复杂计算并返回一个值时,可以使用函数。函数必须包含 @return。
$grid-width: 40px;
$gutter-width: 10px;
@function grid-width($n) {
@return $n * $grid-width + ($n - 1) * $gutter-width;
}
#sidebar {
width: grid-width(5); // 计算结果: 5*40 + 4*10 = 240px
}#sidebar {
width: 240px;
}SASS 中的颜色函数
$btnColors: #409eff, #67c23a, #f56c6c, #e6a23c, #909399;
@for $i from 1 through length($btnColors) {
.btn.type-#{$i} {
$color: nth($btnColors, $i);
background: $color;
color: #fff;
&:hover {
background: lighten($color, 10%);
}
&:active {
background: darken($color, 10%);
}
&:disabled {
background: lighten($color, 20%);
}
}
}.btn.type-1 {
background: #409eff;
color: #fff;
}
.btn.type-1:hover {
background: #73b8ff;
}
.btn.type-1:active {
background: #0d84ff;
}
.btn.type-1:disabled {
background: #a6d2ff;
}
.btn.type-2 {
background: #67c23a;
color: #fff;
}
.btn.type-2:hover {
background: #85cf60;
}
.btn.type-2:active {
background: #529b2e;
}
.btn.type-2:disabled {
background: #a3db87;
}
.btn.type-3 {
background: #f56c6c;
color: #fff;
}
.btn.type-3:hover {
background: #f89c9c;
}
.btn.type-3:active {
background: #f23c3c;
}
.btn.type-3:disabled {
background: #fbcccc;
}
.btn.type-4 {
background: #e6a23c;
color: #fff;
}
.btn.type-4:hover {
background: #ecb869;
}
.btn.type-4:active {
background: #d48a1b;
}
.btn.type-4:disabled {
background: #f2cd96;
}
.btn.type-5 {
background: #909399;
color: #fff;
}
.btn.type-5:hover {
background: #abadb1;
}
.btn.type-5:active {
background: #767980;
}
.btn.type-5:disabled {
background: #c5c7ca;
}五、输出格式
在编译 SCSS 时,可以指定输出的 CSS 格式,以适应不同环境:
:nested:嵌套格式,缩进反映结构。:expanded:易读的展开格式(默认)。:compact:紧凑格式,每个选择器占一行。:compressed:压缩格式,去除所有空白字符,适合生产环境。
需要我帮你配一个 vitepress 的 _navbar.json 导航栏配置吗?方便你直接粘贴使用。
非常抱歉,之前为了追求文章结构的精简,确实遗漏了你笔记中关于“注释”这一重要特性的内容。
在 SCSS 中,注释机制非常灵活,不仅支持原生 CSS 的多行注释,还引入了单行注释,甚至支持在压缩模式下强制保留注释。这部分对于团队协作和代码维护非常重要。
我已经为你单独整理了这部分内容,并同样采用了 VitePress 的 code-group 格式,你可以直接将其插入到博客文章的任意合适位置(例如放在“一、CSS 功能拓展”之后,或作为独立的一节):
六、注释机制
SCSS 支持两种常规的注释方法,它们在编译后的 CSS 中有着截然不同的表现。此外,SCSS 还支持通过插值 #{} 在注释中动态输出变量的值。
// 1. 单行注释:以 // 开头,直到行尾结束
// 这类注释仅存在于 SCSS 源码中,编译后不会输出到 CSS 文件中
// 非常适合用于内部开发笔记或临时禁用某行代码
// 2. 多行注释:以 /* 开始,以 */ 结束
/*
* 这类注释会被完整编译并输出到 CSS 文件中
* 适合用于编写模块说明、版权声明等需要保留的信息
*/
/* 3. 强制保留注释:以 /*! 开始
* 即使在生产环境的 compressed(压缩)模式下,
* 以 ! 开头的注释也会被强制保留在最终的 CSS 中
*/
/*!
* 版权信息:Copyright © 2024 My Project
* 此注释在压缩模式下依然保留
*/
// 4. 注释中的插值
$version: '1.0.0';
/* 当前样式库版本: #{$version} *//*
* 这类注释会被完整编译并输出到 CSS 文件中
* 适合用于编写模块说明、版权声明等需要保留的信息
*/
/* 3. 强制保留注释:以 /*! 开始
* 即使在生产环境的 compressed(压缩)模式下,
* 以 ! 开头的注释也会被强制保留在最终的 CSS 中
*/
/*!
* 版权信息:Copyright © 2024 My Project
* 此注释在压缩模式下依然保留
*/
/* 当前样式库版本: 1.0.0 */七、实际使用
使用SASS实现主题切换
$themes: (
light: (
textColor: #333,
bgColor: #fff
),
dark: (
textColor: #fff,
bgColor: #333
)
);
$themeMap: ();
@function getVar($paramName) {
@return map-get($themeMap, $paramName);
}
@mixin useTheme() {
@each $key, $value in $themes {
$themeMap: $value !global;
html [data-theme='#{$key}'] & {
@content;
}
}
}
.item {
font-size: 14px;
@include useTheme {
background: getVar('bgColor');
color: getVar('textColor');
}
}.item {
font-size: 14px;
}
html [data-theme='light'] .item {
background: #fff;
color: #333;
}
html [data-theme='dark'] .item {
background: #333;
color: #fff;
}用Sass简化媒介查询
// 1. 函数的参数默认值
@mixin flex {
display: flex;
align-items: center;
justify-content: center;
}
.container {
@include flex;
}
// 2. 函数的参数默认值 + 内容块
@mixin flex($layout) {
display: flex;
align-items: $layout;
justify-content: center;
@content;
}
.container {
@include flex(start) {
gap: 20px;
}
}
// 3. 函数的参数默认值 + 响应式
@mixin respondTo($breakname) {
@if $breakname == 'phone' {
@media (min-width: 320px) and (max-width: 480px) {
@content;
}
}
@if $breakname == 'pad' {
@media (min-width: 481px) and (max-width: 768px) {
@content;
}
}
}
.header {
width: 100%;
@include respondTo('phone') {
height: 50px;
}
@include respondTo('pad') {
height: 60px;
}
}
// 4. 媒介查询的参数是对象
$breakpoints: (
phone: (
320px,
480px
),
pad: (
481px,
768px
),
notebook: (
769px,
1024px
),
desktop: (
1025px,
1280px
),
tv: 1281px
);
@mixin respondTo($breakname) {
$bp: map-get($breakpoints, $breakname);
@if type-of($bp) == 'list' {
$min: nth($bp, 1);
$max: nth($bp, 2);
@media (min-width: $min) and (max-width: $max) {
@content;
}
} @else {
@media (min-width: $bp) {
@content;
}
}
}.container {
display: flex;
align-items: center;
justify-content: center;
}
.container {
display: flex;
align-items: start;
justify-content: center;
gap: 20px;
}
.header {
width: 100%;
}
@media (min-width: 320px) and (max-width: 480px) {
.header {
height: 50px;
}
}
@media (min-width: 481px) and (max-width: 768px) {
.header {
height: 60px;
}
}