Skip to content
On this page

Sass 混合宏与继承

Sass的混合宏(Mixins)和继承(Extend)是两个强大的功能,它们允许您创建可重用的样式代码块。本章将详细介绍这两种功能的使用方法、区别和最佳实践。

混合宏 (Mixins)

混合宏允许您定义可重用的样式块,可以接受参数并包含复杂的逻辑。

基本混合宏

scss
// 定义一个简单的混合宏
@mixin border-radius($radius) {
  -webkit-border-radius: $radius;
  -moz-border-radius: $radius;
  -ms-border-radius: $radius;
  border-radius: $radius;
}

// 使用混合宏
.box {
  @include border-radius(10px);
}

// 定义带多个参数的混合宏
@mixin button-style($bg-color, $text-color, $padding: 10px 20px) {
  background-color: $bg-color;
  color: $text-color;
  padding: $padding;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.3s ease;
  
  &:hover {
    background-color: darken($bg-color, 10%);
  }
}

// 使用混合宏
.primary-btn {
  @include button-style(#007bff, white, 12px 24px);
}

.secondary-btn {
  @include button-style(#6c757d, white);
}

带默认值的参数

scss
@mixin shadow($x: 0, $y: 0, $blur: 5px, $spread: 0, $color: rgba(0,0,0,0.1)) {
  -webkit-box-shadow: $x $y $blur $spread $color;
  -moz-box-shadow: $x $y $blur $spread $color;
  box-shadow: $x $y $blur $spread $color;
}

// 使用默认值
.card {
  @include shadow;
}

// 覆盖部分默认值
.featured-card {
  @include shadow(2px, 4px, 8px, 0, rgba(0,0,0,0.15));
}

// 覆盖所有值
notification {
  @include shadow(0, 2px, 10px, 2px, rgba(255,0,0,0.3));
}

可变参数 (Variable Arguments)

使用...来接受可变数量的参数:

scss
// 接受多个颜色参数
@mixin gradient($direction, $colors...) {
  background: nth($colors, 1);
  background: -webkit-linear-gradient($direction, $colors);
  background: -moz-linear-gradient($direction, $colors);
  background: -o-linear-gradient($direction, $colors);
  background: linear-gradient($direction, $colors);
}

// 使用可变参数
.header {
  @include gradient(to right, #ff0000, #00ff00, #0000ff);
}

// 接受多个盒子阴影
@mixin multi-shadow($shadows...) {
  @if length($shadows) > 0 {
    box-shadow: $shadows;
  }
}

.element {
  @include multi-shadow(
    0 1px 3px rgba(0,0,0,0.12),
    0 1px 2px rgba(0,0,0,0.24),
    inset 0 1px 0 rgba(255,255,255,0.2)
  );
}

@content 指令

允许在混合宏中插入额外内容:

scss
@mixin media-query($device) {
  @if $device == mobile {
    @media screen and (max-width: 768px) { @content; }
  }
  @else if $device == tablet {
    @media screen and (min-width: 769px) and (max-width: 1024px) { @content; }
  }
  @else if $device == desktop {
    @media screen and (min-width: 1025px) { @content; }
  }
}

// 使用@content
.sidebar {
  width: 300px;
  
  @include media-query(mobile) {
    width: 100%;
    margin-bottom: 1rem;
  }
  
  @include media-query(tablet) {
    width: 250px;
  }
}

// 高级@content用法:创建包装器
@mixin wrapper($class-name) {
  .#{$class-name} {
    @content;
  }
}

@include wrapper(alert) {
  padding: 1rem;
  border: 1px solid #ccc;
  border-radius: 4px;
  
  &.success {
    border-color: #28a745;
    background-color: #d4edda;
  }
  
  &.error {
    border-color: #dc3545;
    background-color: #f8d7da;
  }
}

条件混合宏

scss
@mixin theme-style($theme) {
  @if $theme == light {
    background-color: white;
    color: #333;
    border: 1px solid #ddd;
  }
  @else if $theme == dark {
    background-color: #333;
    color: white;
    border: 1px solid #555;
  }
  @else if $theme == primary {
    background-color: #007bff;
    color: white;
    border: 1px solid #0062cc;
  }
  @else {
    background-color: #f8f9fa;
    color: #666;
    border: 1px solid #e9ecef;
  }
}

.light-component {
  @include theme-style(light);
}

.dark-component {
  @include theme-style(dark);
}

.primary-component {
  @include theme-style(primary);
}

循环与混合宏

scss
// 创建响应式网格类
@mixin grid-columns($max-columns: 12) {
  @for $i from 1 through $max-columns {
    .col-#{$i} {
      width: percentage($i / $max-columns);
    }
  }
}

@include grid-columns(12);

// 基于映射创建样式
$button-variants: (
  primary: #007bff,
  secondary: #6c757d,
  success: #28a745,
  danger: #dc3545,
  warning: #ffc107,
  info: #17a2b8
);

@mixin button-variant($color) {
  background-color: $color;
  border-color: $color;
  color: if(lightness($color) > 50, #212529, white);
  
  &:hover {
    background-color: darken($color, 7.5%);
    border-color: darken($color, 10%);
    color: if(lightness($color) > 50, #212529, white);
  }
  
  &.disabled,
  &:disabled {
    background-color: $color;
    border-color: $color;
  }
}

@each $name, $color in $button-variants {
  .btn-#{$name} {
    @include button-variant($color);
  }
}

继承 (Extend/Inheritance)

继承允许一个选择器继承另一个选择器的所有样式。

基本继承

scss
// 基础消息样式
.message {
  border: 1px solid #ccc;
  padding: 1rem;
  border-radius: 4px;
  margin-bottom: 1rem;
}

// 继承基础样式
.success {
  @extend .message;
  border-color: #28a745;
  background-color: #d4edda;
  color: #155724;
}

.error {
  @extend .message;
  border-color: #dc3545;
  background-color: #f8d7da;
  color: #721c24;
}

.warning {
  @extend .message;
  border-color: #ffc107;
  background-color: #fff3cd;
  color: #856404;
}

// 编译后的CSS会优化选择器
// .message, .success, .error, .warning { ... }

继承与伪类

scss
.btn {
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
  
  &:hover {
    text-decoration: none;
  }
  
  &:focus {
    outline: 2px solid #007bff;
    outline-offset: 2px;
  }
}

.btn-primary {
  @extend .btn;
  background-color: #007bff;
  color: white;
}

.btn-secondary {
  @extend .btn;
  background-color: #6c757d;
  color: white;
}

占位符选择器 (Placeholder Selectors)

使用%创建占位符选择器,只在被继承时才生成CSS:

scss
// 占位符选择器
%button-base {
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
  transition: all 0.3s ease;
  
  &:hover {
    transform: translateY(-2px);
  }
}

%form-element {
  width: 100%;
  padding: 0.75rem;
  border: 1px solid #ced4da;
  border-radius: 4px;
  font-size: 1rem;
  
  &:focus {
    outline: none;
    border-color: #007bff;
    box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
  }
}

// 继承占位符选择器
.btn {
  @extend %button-base;
  background-color: #007bff;
  color: white;
}

.form-input {
  @extend %form-element;
}

复杂继承场景

scss
%card-base {
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  overflow: hidden;
}

%card-section {
  padding: 1rem;
  
  &:first-child {
    padding-top: 1rem;
  }
  
  &:last-child {
    padding-bottom: 1rem;
  }
}

// 使用继承
.article-card {
  @extend %card-base;
  
  .card-header {
    @extend %card-section;
    border-bottom: 1px solid #eee;
    font-weight: bold;
  }
  
  .card-body {
    @extend %card-section;
  }
  
  .card-footer {
    @extend %card-section;
    border-top: 1px solid #eee;
    background-color: #f8f9fa;
  }
}

混合宏 vs 继承

混合宏的优势

scss
// 混合宏:每个使用的地方都会生成完整样式
@mixin responsive-padding($mobile: 1rem, $desktop: 2rem) {
  padding: $mobile;
  
  @media (min-width: 768px) {
    padding: $desktop;
  }
}

// 每次使用都会生成完整的媒体查询
.element1 {
  @include responsive-padding(1rem, 2rem);
}

.element2 {
  @include responsive-padding(0.5rem, 1.5rem);
}

// 这种方式适合需要不同参数的情况

继承的优势

scss
// 继承:共享相同样式,生成优化的CSS
%responsive-base {
  padding: 1rem;
  
  @media (min-width: 768px) {
    padding: 2rem;
  }
}

.component1 {
  @extend %responsive-base;
  background-color: #f8f9fa;
}

.component2 {
  @extend %responsive-base;
  background-color: #e9ecef;
}

// 编译后会生成优化的选择器组合

高级混合宏模式

工厂模式混合宏

scss
@mixin create-component($name, $styles) {
  .#{$name} {
    @each $property, $value in $styles {
      #{$property}: $value;
    }
  }
}

// 使用工厂模式创建组件
@include create-component(button, (
  padding: 10px 20px,
  background-color: #007bff,
  color: white,
  border: none,
  border-radius: 4px
));

@include create-component(card, (
  background: white,
  padding: 1rem,
  border-radius: 8px,
  box-shadow: 0 2px 4px rgba(0,0,0,0.1)
));

配置驱动的混合宏

scss
// 创建可配置的组件
@mixin configurable-component($config) {
  @if map-get($config, has-padding) {
    padding: map-get($config, padding-size, 1rem);
  }
  
  @if map-get($config, has-border) {
    border: map-get($config, border-width, 1px) solid map-get($config, border-color, #ccc);
  }
  
  @if map-get($config, has-shadow) {
    box-shadow: map-get($config, shadow-value, 0 2px 4px rgba(0,0,0,0.1));
  }
  
  @if map-get($config, has-radius) {
    border-radius: map-get($config, radius-size, 4px);
  }
}

// 配置映射
$card-config: (
  has-padding: true,
  padding-size: 1.5rem,
  has-border: true,
  border-color: #dee2e6,
  has-shadow: true,
  has-radius: true,
  radius-size: 8px
);

$button-config: (
  has-padding: true,
  padding-size: 0.5rem 1rem,
  has-border: false,
  has-shadow: false,
  has-radius: true
);

.card {
  @include configurable-component($card-config);
}

.btn {
  @include configurable-component($button-config);
}

最佳实践

1. 何时使用混合宏

scss
// 适合使用混合宏的场景:需要参数变化
@mixin responsive-font($mobile: 1rem, $tablet: 1.1rem, $desktop: 1.2rem) {
  font-size: $mobile;
  
  @media (min-width: 768px) {
    font-size: $tablet;
  }
  
  @media (min-width: 1024px) {
    font-size: $desktop;
  }
}

.title {
  @include responsive-font(1.5rem, 1.7rem, 2rem);
}

.subtitle {
  @include responsive-font(1.2rem, 1.3rem, 1.5rem);
}

2. 何时使用继承

scss
// 适合使用继承的场景:共享相同基础样式
%form-control {
  display: block;
  width: 100%;
  padding: 0.75rem;
  font-size: 1rem;
  line-height: 1.5;
  color: #495057;
  background-color: #fff;
  background-clip: padding-box;
  border: 1px solid #ced4da;
  border-radius: 0.25rem;
  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}

.form-input {
  @extend %form-control;
}

.form-textarea {
  @extend %form-control;
  height: auto;
}

.form-select {
  @extend %form-control;
}

3. 混合宏和继承的组合使用

scss
// 创建基础样式(使用占位符)
%button-base {
  display: inline-block;
  font-weight: 400;
  text-align: center;
  white-space: nowrap;
  vertical-align: middle;
  user-select: none;
  border: 1px solid transparent;
  padding: 0.5rem 1rem;
  font-size: 1rem;
  line-height: 1.5;
  border-radius: 0.25rem;
  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}

// 定义样式变化的混合宏
@mixin button-variant($background, $border, $color: white) {
  @extend %button-base;
  background-color: $background;
  border-color: $border;
  color: $color;
  
  &:hover {
    background-color: darken($background, 7.5%);
    border-color: darken($border, 10%);
    color: $color;
  }
  
  &:focus {
    box-shadow: 0 0 0 0.2rem rgba($background, 0.5);
  }
}

.btn-primary {
  @include button-variant(#007bff, #007bff);
}

.btn-success {
  @include button-variant(#28a745, #28a745);
}

.btn-danger {
  @include button-variant(#dc3545, #dc3545);
}

通过合理使用混合宏和继承,您可以创建灵活、可重用和易于维护的Sass代码。