Sass 提供了三种代码复用机制:混合宏(Mixin) 、继承/占位符(@extend / %placeholder) 和 函数(@function) 。理解它们的区别和适用场景,是写出高质量、可维护 SCSS 的关键。
一、@mixin 和 @include 基本用法 Mixin 将一组 CSS 声明封装为一个可复用的块,通过 @include 引入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @mixin flex-center { display : flex; justify-content : center; align-items : center; } .modal-overlay { @include flex-center; position : fixed; inset: 0 ; } .card { @include flex-center; flex-direction : column; }
带参数的 Mixin 1 2 3 4 5 6 7 8 9 10 11 12 @mixin size($width , $height : $width ) { width : $width ; height : $height ; } .avatar { @include size(40px ); } .cover { @include size(100% , 200px ); }
关键字参数 参数较多时使用关键字参数,可读性更好,且无需按顺序传参:
1 2 3 4 5 6 7 8 9 10 11 12 @mixin card($bg : white, $radius : 4px , $shadow : 0 2px 4px rgba(0 ,0 ,0 ,.1 )) { background : $bg ; border-radius : $radius ; box-shadow : $shadow ; } .promotion-card { @include card( $bg : linear-gradient(135deg , #667eea , #764ba2 ), $shadow : 0 4px 15px rgba(102 , 126 , 234 , .4 ) ); }
可变参数(…) 当参数数量不确定时,使用 ... 收集多个值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @mixin box-shadow($shadows ...) { box-shadow : $shadows ; } .card { @include box-shadow( 0 1px 3px rgba(0 ,0 ,0 ,.1 ), 0 1px 2px rgba(0 ,0 ,0 ,.06 ) ); } $shadows : (0 1px 3px rgba(0 ,0 ,0 ,.1 ), 0 1px 2px rgba(0 ,0 ,0 ,.06 ));.card { @include box-shadow($shadows ...); }
@content — 内容块传递 Mixin 可以接收一段自定义样式块,在内部通过 @content 插入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @mixin respond-to($breakpoint ) { @if $breakpoint == mobile { @media (max-width : 767px ) { @content ; } } @else if $breakpoint == tablet { @media (min-width : 768px ) and (max-width : 1023px ) { @content ; } } @else if $breakpoint == desktop { @media (min-width : 1024px ) { @content ; } } } .sidebar { width : 300px ; @include respond-to(mobile) { width : 100% ; display : none; } }
编译结果:
1 2 3 4 .sidebar { width : 300px ; }@media (max-width : 767px ) { .sidebar { width : 100% ; display : none; } }
@content 多次使用 可以在一个 mixin 中使用多次 @content,每次传入不同的内容块:
1 2 3 4 5 6 7 8 9 10 @mixin hover-active { &:hover { @content ; } &:active { @content ; } } .btn { @include hover-active { opacity : 0.8 ; } }
二、@extend 和 %placeholder @extend 的基本用法 @extend 让一个选择器继承另一个选择器的样式——编译时会通过选择器分组(,)来实现:
1 2 3 4 5 6 7 8 9 .error { border : 1px solid red; color : red; } .serious-error { @extend .error; border-width : 3px ; }
编译结果:
1 2 .error , .serious-error { border : 1px solid red; color : red; }.serious-error { border-width : 3px ; }
%placeholder 占位符 %placeholder 定义只用于继承的样式块——它本身不会 生成任何 CSS,只在被 @extend 时才输出:
1 2 3 4 5 6 7 8 %ellipsis { overflow : hidden; text-overflow : ellipsis; white-space : nowrap; } .title { @extend %ellipsis; width : 200px ; }.desc { @extend %ellipsis; width : 100% ; }
编译结果:
1 2 3 .title , .desc { overflow : hidden; text-overflow : ellipsis; white-space : nowrap; }.title { width : 200px ; }.desc { width : 100% ; }
@extend 嵌套选择器 @extend 可以继承一个复杂选择器:
1 2 3 4 5 6 7 8 9 .icon { transition : transform .2s ; .dark & { filter : brightness(.8 ); } } .close-icon { @extend .icon; font-size : 20px ; }
编译结果中,.dark .icon 的规则也会被复制为 .dark .close-icon。
@extend 的陷阱 不能跨 @media 块 :@extend 只能在同层使用,媒体查询内外不可互相 extend。1 2 3 4 5 .base { color : red; }@media screen { .derived { @extend .base; } }
可能产生意外的选择器分组 :当 .a extend 了 .b,所有用到 .b 的规则都会带上 .a。在大项目中这可能造成 CSS 体积膨胀和意外覆盖。
不要 extend 未定义在项目中的第三方 class :会导致第三方样式也被连带引入。
三、@mixin vs @extend 对比 对比维度 @mixin @extend + %placeholder 输出方式 每次 @include 复制一份样式代码 选择器分组,共用一份样式 CSS 体积 较大(代码重复) 较小(合并选择器) 传参能力 ✅ 支持参数 ❌ 不支持 逻辑能力 ✅ 可配合 @if/@for 等控制指令 ❌ 纯样式复用 跨媒体查询 ✅ 可以 ❌ 不可以 适用场景 需要传参、带逻辑、有变化的复用 无参数的纯粹样式复用,如 %clearfix、%ellipsis
经验法则 :
需要传参 → 用 @mixin 需要在 media query 中使用 → 用 @mixin 纯粹复用一组固定样式,不需要变化 → 用 %placeholder + @extend 不确定时默认用 @mixin 四、@function 和 @return 函数与 mixin 的区别:mixin 输出样式块,函数返回一个值 。
1 2 3 4 5 6 7 8 @function px-to-rem($px , $base : 16px ) { @return math.div($px , $base ) * 1rem ; } .card { padding : px-to-rem(16px ); font-size : px-to-rem(14px ); }
实用函数示例 1. z-index 层级管理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $z-layers : ( modal: 1000 , dropdown: 100 , header: 10 , default: 1 , ); @function z($layer ) { @if map-has-key($z-layers , $layer ) { @return map-get($z-layers , $layer ); } @else { @error "未知层级 #{$layer},可用值: #{map-keys($z-layers)}" ; } } .modal { z-index : z(modal); } .header { z-index : z(header); }
2. 颜色对比度计算 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @function contrasting-color($bg ) { @if lightness($bg ) > 50 { @return #333 ; } @else { @return #fff ; } } .dark-bg { background : #333 ; color : contrasting-color(#333 ); } .light-bg { background : #eee ; color : contrasting-color(#eee ); }
内置函数概览 Sass 提供了丰富的内置函数,全部可通过 @use 'sass:*' 使用:
数学函数 1 2 3 4 5 6 7 8 9 10 @use 'sass:math' ;math.ceil (4.2 ); math.floor (4.8 ); math.round (4.5 ); math.min (1 , 3 , 2 ); math.max (1 , 3 , 2 ); math.random (); math.random (100 ); math.div (10 , 3 );
字符串函数 1 2 3 4 5 6 7 8 @use 'sass:string' ;string.to-upper-case ('hello'); string.to-lower-case ('WORLD'); string.length ('hello'); string.slice ('hello', 2 , 4 ); string.insert ('hello', 'xx', 3 ); string.index ('hello', 'l');
颜色函数 1 2 3 4 5 6 7 8 9 @use 'sass:color' ;color .adjust (#409 eff, $lightness: 10% ); color .adjust (#409 eff, $lightness: -10% ); color .adjust (#409 eff, $alpha: -0.3 ); color .mix (#409 eff, #67 c23a, 50% ); color .complement (#409 eff); color .grayscale (#409 eff);
历史背景:旧版 Sass 中 lighten($color, 10%)、darken() 等是全局函数;Dart Sass 将它们移入 sass:color 模块。虽然全局调用仍然兼容,建议新代码通过 @use 'sass:color' 访问。
列表函数 1 2 3 4 5 6 7 8 9 @use 'sass:list' ;list.nth ((a b c), 2); list.length ((a b c)); list.index ((a b c), b ); list.append ((a b), c); list.join ((a b), (c d)); list.separator ((a b)); list.is-bracketed ([a b]);
Map 函数 1 2 3 4 5 6 7 8 9 @use 'sass:map' ;map.get ((a: 1 , b: 2 ), a ); map.has-key ((a: 1 ), a ); map.keys ((a: 1 , b: 2 )); map.values ((a: 1 , b: 2 )); map.merge ((a: 1 ), (b: 2 )); map.remove ((a: 1 , b: 2 ), a ); map.deep-merge ((a: (x: 1 )), (a: (y:2 )));
选择器函数 1 2 3 4 5 6 @use 'sass:selector' ;selector.nest ('.a', '.b'); selector.append ('.a', '.b'); selector.replace ('.a .b', '.b', '.c'); selector.parse ('.a > .b');
五、插值 #{} 插值允许在 CSS 选择器、属性名、属性值中使用 SassScript 表达式:
选择器插值 1 2 3 4 $prefix : 'el' ;.#{$prefix }-button { ... } .#{$prefix }-input { ... }
属性名插值 1 2 3 4 5 6 7 8 9 10 $direction : 'margin' ;.#{$direction }-top { ... } @for $i from 1 through 12 { .col-span- #{$i } { grid-column: span $i ; } }
属性值插值 1 2 3 4 5 $icon : '★' ;.icon-star ::before { content : '#{$icon}' ; }
在注释中插值 在 @mixin 和 @function 名称中插值 1 2 3 4 5 6 7 8 9 10 @mixin color-modifier($color ) { &--#{$color } { border-color : $color ; } } .card { @include color-modifier(primary); @include color-modifier(success); }
六、选择建议总结 需求 方案 复用一段无参的静态样式 %placeholder + @extend复用带参数、逻辑的样式 @mixin + @include计算并返回一个值(px→rem、z-index) @function + @return条件渲染 @if / @else 配合 mixin 或 function批量生成类名 @for 或 @each 配合插值 #{}动态拼接选择器/属性名 插值 #{}