控制指令
SCSS/SASS控制指令
控制指令的概念与意义
控制指令是Sass中的程序化功能,允许你使用条件判断、循环和其他逻辑结构来动态生成CSS。这些指令使Sass具有了编程语言的特性,大大增强了样式表的灵活性和可维护性。
控制指令的主要优势:
- 减少重复代码:通过循环自动生成重复的样式规则
- 条件样式:根据不同条件生成不同的样式
- 动态计算:基于变量和表达式动态生成值
- 增强可维护性:集中管理逻辑,便于修改和扩展
条件语句(@if/@else)
Sass支持条件逻辑,可以根据条件生成不同的样式:
$theme: 'dark';
.button {
@if $theme == 'dark' {
background-color: #333;
color: white;
} @else if $theme == 'light' {
background-color: #f8f9fa;
color: #333;
} @else {
background-color: #007bff;
color: white;
}
}
条件表达式
Sass支持多种条件表达式:
// 比较运算符
$size: 'medium';
.element {
@if $size == 'small' {
padding: 0.5rem;
} @else if $size == 'medium' {
padding: 1rem;
} @else if $size == 'large' {
padding: 2rem;
}
// 逻辑运算符
$primary: true;
$important: true;
@if $primary and $important {
font-weight: bold;
color: red;
}
// 否定运算符
$disabled: false;
@if not $disabled {
cursor: pointer;
}
// 复杂条件
$screen-size: 768px;
$is-mobile: true;
@if $screen-size < 992px and $is-mobile {
display: block;
}
}
三元表达式
Sass也支持类似三元运算符的if()函数:
$theme: 'dark';
.element {
background-color: if($theme == 'dark', #333, #fff);
color: if($theme == 'dark', #fff, #333);
border: 1px solid if($theme == 'dark', #555, #ddd);
}
循环语句
Sass提供了三种循环结构:@for、@each和@while。
@for循环
@for循环有两种形式:
@for $var from <start> through <end>
- 包含结束值@for $var from <start> to <end>
- 不包含结束值
// through包含结束值
@for $i from 1 through 3 {
.col-#{$i} {
width: 100% / $i;
}
}
// 编译结果
.col-1 {
width: 100%;
}
.col-2 {
width: 50%;
}
.col-3 {
width: 33.3333%;
}
// to不包含结束值
@for $i from 1 to 4 {
.margin-#{$i} {
margin: #{$i}rem;
}
}
// 编译结果
.margin-1 {
margin: 1rem;
}
.margin-2 {
margin: 2rem;
}
.margin-3 {
margin: 3rem;
}
实际应用:栅格系统
$columns: 12;
@for $i from 1 through $columns {
.col-#{$i} {
width: percentage($i / $columns);
}
}
// 响应式栅格
$breakpoints: (
'sm': 576px,
'md': 768px,
'lg': 992px,
'xl': 1200px
);
@each $breakpoint, $width in $breakpoints {
@media (min-width: $width) {
@for $i from 1 through $columns {
.col-#{$breakpoint}-#{$i} {
width: percentage($i / $columns);
}
}
}
}
@each循环
@each循环用于遍历列表或映射:
// 遍历列表
$sizes: 'small', 'medium', 'large';
@each $size in $sizes {
.btn-#{$size} {
@if $size == 'small' {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
} @else if $size == 'medium' {
padding: 0.5rem 1rem;
font-size: 1rem;
} @else if $size == 'large' {
padding: 0.75rem 1.5rem;
font-size: 1.25rem;
}
}
}
// 遍历映射
$colors: (
'primary': #007bff,
'secondary': #6c757d,
'success': #28a745,
'danger': #dc3545,
'warning': #ffc107,
'info': #17a2b8
);
@each $name, $color in $colors {
.text-#{$name} {
color: $color;
}
.bg-#{$name} {
background-color: $color;
}
.border-#{$name} {
border-color: $color;
}
}
多变量遍历
@each也支持同时遍历多个值:
// 多值列表
$buttons: (
'primary' #007bff #0069d9,
'success' #28a745 #218838,
'danger' #dc3545 #c82333
);
@each $name, $bg, $hover in $buttons {
.btn-#{$name} {
background-color: $bg;
&:hover {
background-color: $hover;
}
}
}
// 嵌套列表
$shadows: (
'small': (0 2px 4px rgba(0, 0, 0, 0.1)),
'medium': (0 4px 8px rgba(0, 0, 0, 0.1)),
'large': (0 8px 16px rgba(0, 0, 0, 0.1))
);
@each $size, $shadow in $shadows {
.shadow-#{$size} {
box-shadow: $shadow;
}
}
@while循环
@while循环会一直执行,直到条件不再满足:
$i: 6;
@while $i > 0 {
.item-#{$i} {
width: 2em * $i;
}
$i: $i - 2;
}
// 编译结果
.item-6 {
width: 12em;
}
.item-4 {
width: 8em;
}
.item-2 {
width: 4em;
}
实际应用:生成渐变色
$base-color: #007bff;
$steps: 5;
$i: 1;
@while $i <= $steps {
.gradient-#{$i} {
background-color: mix(white, $base-color, ($i - 1) * 20%);
}
$i: $i + 1;
}
// 编译结果
.gradient-1 {
background-color: #007bff; // 0% 白色
}
.gradient-2 {
background-color: #3395ff; // 20% 白色
}
.gradient-3 {
background-color: #66afff; // 40% 白色
}
.gradient-4 {
background-color: #99caff; // 60% 白色
}
.gradient-5 {
background-color: #cce4ff; // 80% 白色
}
@debug、@warn和@error指令
Sass提供了三个用于调试和错误处理的指令:
@debug
@debug指令用于在编译过程中输出信息,不会影响生成的CSS:
$width: 100px;
@debug "Current width is #{$width}";
$map: (
'key1': 'value1',
'key2': 'value2'
);
@debug map-get($map, 'key1');
@debug type-of($width);
@warn
@warn指令用于显示警告信息,但不会停止编译:
@mixin deprecated($message) {
@warn "Deprecated: #{$message}";
}
@include deprecated("This mixin will be removed in the next version");
@mixin grid-width($n) {
@if not is-unitless($n) {
@warn "The argument $n = #{$n} should be unitless.";
}
width: percentage($n / 12);
}
.element {
@include grid-width(6px); // 警告:参数应该无单位
}
@error
@error指令用于显示错误信息并停止编译:
@mixin theme-colors($theme) {
@if $theme == 'light' {
background-color: white;
color: black;
} @else if $theme == 'dark' {
background-color: black;
color: white;
} @else {
@error "Unknown theme: #{$theme}";
}
}
.element {
@include theme-colors('invalid'); // 错误:未知主题
}
@function get-color($name) {
$colors: (
'primary': #007bff,
'secondary': #6c757d
);
@if not map-has-key($colors, $name) {
@error "Invalid color name: #{$name}";
}
@return map-get($colors, $name);
}
.element {
color: get-color('tertiary'); // 错误:无效的颜色名称
}
控制指令的高级应用
递归混入
使用控制指令可以创建递归混入,这在生成复杂结构时非常有用:
// 递归生成嵌套选择器
@mixin nested-selectors($selector, $depth, $max-depth) {
#{$selector} {
padding: 10px * $depth;
border: 1px solid rgba(0, 0, 0, 0.1 * $depth);
@if $depth < $max-depth {
@include nested-selectors("#{$selector} > *", $depth + 1, $max-depth);
}
}
}
@include nested-selectors(".nested", 1, 3);
// 编译结果
.nested {
padding: 10px;
border: 1px solid rgba(0, 0, 0, 0.1);
}
.nested > * {
padding: 20px;
border: 1px solid rgba(0, 0, 0, 0.2);
}
.nested > * > * {
padding: 30px;
border: 1px solid rgba(0, 0, 0, 0.3);
}
动态生成媒体查询
$breakpoints: (
'xs': 0,
'sm': 576px,
'md': 768px,
'lg': 992px,
'xl': 1200px
);
@mixin media-up($breakpoint) {
@if map-has-key($breakpoints, $breakpoint) {
$min-width: map-get($breakpoints, $breakpoint);
@media (min-width: $min-width) {
@content;
}
} @else {
@error "Unknown breakpoint: #{$breakpoint}";
}
}
@mixin media-down($breakpoint) {
@if map-has-key($breakpoints, $breakpoint) {
$max-width: map-get($breakpoints, $breakpoint) - 0.02;
@media (max-width: $max-width) {
@content;
}
} @else {
@error "Unknown breakpoint: #{$breakpoint}";
}
}
.responsive-element {
font-size: 14px;
@include media-up('md') {
font-size: 16px;
}
@include media-up('lg') {
font-size: 18px;
}
}
条件混入
// 根据条件应用样式
@mixin apply-if($condition, $styles...) {
@if $condition {
@each $property, $value in $styles {
#{$property}: $value;
}
}
}
.element {
$is-important: true;
@include apply-if($is-important,
(font-weight, bold),
(color, red),
(text-transform, uppercase)
);
}
// 编译结果
.element {
font-weight: bold;
color: red;
text-transform: uppercase;
}
动态生成动画
@mixin keyframes($name, $steps) {
@keyframes #{$name} {
@for $i from 0 through length($steps) - 1 {
$step: nth($steps, $i + 1);
$percentage: percentage($i / (length($steps) - 1));
#{$percentage} {
transform: translateY(#{nth($step, 1)}px);
opacity: nth($step, 2);
}
}
}
}
// 定义动画步骤:[位移, 透明度]
$bounce-steps: (
[0, 1], // 0%
[-30, 1], // 25%
[-15, 1], // 50%
[-5, 1], // 75%
[0, 1] // 100%
);
@include keyframes('bounce', $bounce-steps);
.animated-element {
animation: bounce 1s ease infinite;
}
实际应用案例
主题切换系统
$themes: (
'light': (
'background': #ffffff,
'text': #333333,
'primary': #007bff,
'secondary': #6c757d,
'border': #dee2e6
),
'dark': (
'background': #343a40,
'text': #f8f9fa,
'primary': #3f8cff,
'secondary': #adb5bd,
'border': #495057
)
);
$current-theme: 'light' !default;
@function theme-color($key) {
@return map-get(map-get($themes, $current-theme), $key);
}
@mixin themed() {
@each $theme, $map in $themes {
.theme-#{$theme} & {
$current-theme: $theme !global;
@content;
$current-theme: 'light' !global;
}
}
}
// 使用主题系统
.card {
@include themed() {
background-color: theme-color('background');
color: theme-color('text');
border: 1px solid theme-color('border');
}
.card-header {
@include themed() {
border-bottom: 1px solid theme-color('border');
}
}
.card-title {
@include themed() {
color: theme-color('primary');
}
}
}
响应式工具生成器
// 定义断点
$breakpoints: (
'xs': 0,
'sm': 576px,
'md': 768px,
'lg': 992px,
'xl': 1200px
);
// 定义显示属性
$displays: (
'block',
'inline',
'inline-block',
'flex',
'inline-flex',
'none'
);
// 生成响应式显示类
@each $breakpoint-name, $breakpoint-value in $breakpoints {
@each $display in $displays {
@if $breakpoint-name == 'xs' {
.d-#{$display} {
display: #{$display};
}
} @else {
@media (min-width: $breakpoint-value) {
.d-#{$breakpoint-name}-#{$display} {
display: #{$display};
}
}
}
}
}
// 生成响应式间距类
$spacers: (
0: 0,
1: 0.25rem,
2: 0.5rem,
3: 1rem,
4: 1.5rem,
5: 3rem
);
$sides: (
't': 'top',
'r': 'right',
'b': 'bottom',
'l': 'left',
'x': ('left', 'right'),
'y': ('top', 'bottom'),
'': ('top', 'right', 'bottom', 'left')
);
$properties: (
'm': 'margin',
'p': 'padding'
);
@each $breakpoint-name, $breakpoint-value in $breakpoints {
@each $prop-abbr, $prop in $properties {
@each $side-abbr, $side in $sides {
@each $size, $value in $spacers {
$class-name: if($breakpoint-name == 'xs',
"#{$prop-abbr}#{$side-abbr}-#{$size}",
"#{$prop-abbr}#{$side-abbr}-#{$breakpoint-name}-#{$size}");
@if $breakpoint-name == 'xs' {
.#{$class-name} {
@if type-of($side) == 'list' {
@each $s in $side {
#{$prop}-#{$s}: $value;
}
} @else {
#{$prop}-#{$side}: $value;
}
}
} @else {
@media (min-width: $breakpoint-value) {
.#{$class-name} {
@if type-of($side) == 'list' {
@each $s in $side {
#{$prop}-#{$s}: $value;
}
} @else {
#{$prop}-#{$side}: $value;
}
}
}
}
}
}
}
}
动态生成表单样式
// 定义表单状态
$form-states: (
'default': (
'border': #ced4da,
'background': #fff,
'text': #495057,
'shadow': rgba(0, 123, 255, 0.25)
),
'valid': (
'border': #28a745,
'background': #f8fff9,
'text': #155724,
'shadow': rgba(40, 167, 69, 0.25)
),
'invalid': (
'border': #dc3545,
'background': #fff8f8,
'text': #721c24,
'shadow': rgba(220, 53, 69, 0.25)
)
);
// 定义表单元素
$form-elements: (
'input',
'select',
'textarea'
);
// 生成表单样式
@each $element in $form-elements {
#{$element} {
display: block;
width: 100%;
padding: 0.375rem 0.75rem;
font-size: 1rem;
line-height: 1.5;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
$state: map-get($form-states, 'default');
border: 1px solid map-get($state, 'border');
background-color: map-get($state, 'background');
color: map-get($state, 'text');
&:focus {
outline: 0;
border-color: map-get($state, 'border');
box-shadow: 0 0 0 0.2rem map-get($state, 'shadow');
}
@each $state-name, $state-values in $form-states {
@if $state-name != 'default' {
&.is-#{$state-name} {
border-color: map-get($state-values, 'border');
background-color: map-get($state-values, 'background');
color: map-get($state-values, 'text');
&:focus {
box-shadow: 0 0 0 0.2rem map-get($state-values, 'shadow');
}
}
}
}
}
}
最佳实践
1. 避免过度复杂的逻辑
虽然Sass的控制指令功能强大,但过于复杂的逻辑可能导致代码难以维护:
// 不推荐:过于复杂的嵌套逻辑
@for $i from 1 through 5 {
@for $j from 1 through 5 {
@if $i <= $j {
@for $k from 1 through 3 {
.grid-#{$i}-#{$j}-#{$k} {
// 样式...
}
}
}
}
}
// 推荐:拆分为更小的、可管理的部分
@mixin create-grid-item($i, $j) {
@for $k from 1 through 3 {
.grid-#{$i}-#{$j}-#{$k} {
// 样式...
}
}
}
@for $i from 1 through 5 {
@for $j from 1 through 5 {
@if $i <= $j {
@include create-grid-item($i, $j);
}
}
}
2. 使用有意义的变量名
在控制结构中使用描述性变量名,提高代码可读性:
// 不推荐:不明确的变量名
@for $i from 1 through 12 {
.col-#{$i} {
width: percentage($i / 12);
}
}
// 推荐:描述性变量名
$total-columns: 12;
@for $column-span from 1 through $total-columns {
.col-#{$column-span} {
width: percentage($column-span / $total-columns);
}
}
3. 添加注释
为复杂的控制结构添加注释,解释其目的和工作原理:
// 生成响应式工具类
// 1. 遍历所有断点
// 2. 为每个断点生成显示类
// 3. 特殊处理xs断点(无媒体查询)
@each $breakpoint-name, $breakpoint-value in $breakpoints {
@each $display in $displays {
// xs断点不需要媒体查询包装
@if $breakpoint-name == 'xs' {
.d-#{$display} {
display: #{$display};
}
} @else {
// 其他断点需要媒体查询
@media (min-width: $breakpoint-value) {
.d-#{$breakpoint-name}-#{$display} {
display: #{$display};
}
}
}
}
}
4. 使用函数简化复杂计算
将复杂计算封装到函数中,使控制结构更清晰:
// 不推荐:在循环中直接进行复杂计算
@for $i from 1 through 5 {
.element-#{$i} {
width: 100% / (1 + $i * 0.5);
height: 20px * (1 + log($i) / log(10));
}
}
// 推荐:使用函数封装计算逻辑
@function calculate-width($index) {
@return 100% / (1 + $index * 0.5);
}
@function calculate-height($index) {
@return 20px * (1 + log($index) / log(10));
}
@for $i from 1 through 5 {
.element-#{$i} {
width: calculate-width($i);
height: calculate-height($i);
}
}
5. 避免无限循环
使用@while循环时,确保有明确的终止条件:
// 危险:可能导致无限循环
$i: 1;
@while $i != 10 {
.item-#{$i} {
width: 10% * $i;
}
$i: $i + 2; // 永远不会等于10
}
// 安全:明确的终止条件
$i: 1;
@while $i < 10 {
.item-#{$i} {
width: 10% * $i;
}
$i: $i + 2;
}
控制指令与其他Sass特性的结合
与混入结合
// 定义一个可配置的网格系统混入
@mixin generate-grid($columns: 12, $breakpoints: ()) {
// 基础列样式
.row {
display: flex;
flex-wrap: wrap;
margin-right: -15px;
margin-left: -15px;
}
// 生成基础列
@for $i from 1 through $columns {
.col-#{$i} {
position: relative;
width: 100%;
padding-right: 15px;
padding-left: 15px;
flex: 0 0 percentage($i / $columns);
max-width: percentage($i / $columns);
}
}
// 生成响应式列
@each $breakpoint, $width in $breakpoints {
@media (min-width: $width) {
@for $i from 1 through $columns {
.col-#{$breakpoint}-#{$i} {
flex: 0 0 percentage($i / $columns);
max-width: percentage($i / $columns);
}
}
}
}
}
// 使用混入
@include generate-grid(12, (
'sm': 576px,
'md': 768px,
'lg': 992px,
'xl': 1200px
));
与函数结合
// 定义一个生成颜色变体的函数
@function generate-color-variants($base-color, $variants: ('light', 'dark')) {
$result: (
'base': $base-color
);
@each $variant in $variants {
@if $variant == 'light' {
$result: map-merge($result, ('light': lighten($base-color, 15%)));
} @else if $variant == 'dark' {
$result: map-merge($result, ('dark': darken($base-color, 15%)));
} @else if $variant == 'transparent' {
$result: map-merge($result, ('transparent': rgba($base-color, 0.5)));
}
}
@return $result;
}
// 使用函数
$primary-variants: generate-color-variants(#007bff, ('light', 'dark', 'transparent'));
@each $variant, $color in $primary-variants {
.btn-primary-#{$variant} {
background-color: $color;
}
}
与模块系统结合
// _theme.scss
@mixin generate-theme($name, $colors) {
.theme-#{$name} {
@each $key, $value in $colors {
--color-#{$key}: #{$value};
}
// 生成工具类
@each $key, $value in $colors {
.text-#{$key} {
color: var(--color-#{$key});
}
.bg-#{$key} {
background-color: var(--color-#{$key});
}
.border-#{$key} {
border-color: var(--color-#{$key});
}
}
}
}
// main.scss
@use 'theme';
// 定义主题
$light-theme: (
'primary': #007bff,
'secondary': #6c757d,
'success': #28a745,
'background': #ffffff,
'text': #212529
);
$dark-theme: (
'primary': #3f8cff,
'secondary': #adb5bd,
'success': #48c774,
'background': #343a40,
'text': #f8f9fa
);
// 应用主题
@include theme.generate-theme('light', $light-theme);
@include theme.generate-theme('dark', $dark-theme);
总结
Sass的控制指令为CSS预处理带来了编程语言的强大功能,使我们能够:
- 动态生成样式:通过循环和条件语句自动生成大量样式规则
- 创建智能组件:根据不同条件应用不同样式
- 构建可配置系统:创建高度可定制的样式框架
- 减少重复代码:通过程序化方法减少样式表中的重复
- 增强可维护性:集中管理逻辑,使样式表更易于维护和扩展
控制指令是Sass最强大的特性之一,掌握它们可以显著提高CSS开发的效率和质量。通过合理组合条件语句、循环和其他Sass特性,你可以构建出灵活、可维护且高效的样式系统。
然而,需要记住的是,控制指令的强大功能也带来了复杂性。在实际项目中,应该平衡使用控制指令的便利性与代码可读性和可维护性。遵循本文介绍的最佳实践,可以帮助你充分利用控制指令的优势,同时避免常见的陷阱。
无论是构建简单的组件还是复杂的设计系统,Sass的控制指令都是前端开发者工具箱中不可或缺的工具。