Skip to content
On this page

Sass 响应式设计

Sass为响应式设计提供了强大的支持,通过变量、混合宏、函数和控制指令,可以轻松创建灵活、可维护的响应式样式系统。本章将详细介绍如何使用Sass实现响应式设计。

响应式设计基础

断点变量管理

scss
// 定义断点变量
$breakpoints: (
  'xs': 0,
  'sm': 576px,
  'md': 768px,
  'lg': 992px,
  'xl': 1200px,
  'xxl': 1400px
);

// 使用断点
$mobile-max: map-get($breakpoints, 'sm') - 1px;  // 575px
$tablet-min: map-get($breakpoints, 'sm');        // 576px
$tablet-max: map-get($breakpoints, 'md') - 1px;  // 767px
$desktop-min: map-get($breakpoints, 'lg');       // 992px

// 基于断点的响应式类
.container {
  width: 100%;
  padding: 0 15px;
  
  @media (min-width: map-get($breakpoints, 'sm')) {
    max-width: 540px;
  }
  
  @media (min-width: map-get($breakpoints, 'md')) {
    max-width: 720px;
  }
  
  @media (min-width: map-get($breakpoints, 'lg')) {
    max-width: 960px;
  }
  
  @media (min-width: map-get($breakpoints, 'xl')) {
    max-width: 1140px;
  }
  
  @media (min-width: map-get($breakpoints, 'xxl')) {
    max-width: 1320px;
  }
}

断点混合宏

scss
// 媒体查询混合宏
@mixin respond-to($breakpoint) {
  @if map-has-key($breakpoints, $breakpoint) {
    @media (min-width: map-get($breakpoints, $breakpoint)) {
      @content;
    }
  } @else {
    @media (min-width: $breakpoint) {
      @content;
    }
  }
}

// 使用断点混合宏
.element {
  padding: 1rem;
  
  @include respond-to(sm) {
    padding: 1.5rem;
  }
  
  @include respond-to(md) {
    padding: 2rem;
  }
  
  @include respond-to(lg) {
    padding: 2.5rem;
  }
}

// 更多响应式混合宏
@mixin respond-above($breakpoint) {
  @media (min-width: map-get($breakpoints, $breakpoint)) {
    @content;
  }
}

@mixin respond-below($breakpoint) {
  @media (max-width: map-get($breakpoints, $breakpoint) - 1px) {
    @content;
  }
}

@mixin respond-between($lower, $upper) {
  @media (min-width: map-get($breakpoints, $lower)) and (max-width: map-get($breakpoints, $upper) - 1px) {
    @content;
  }
}

// 使用不同的响应式混合宏
.card {
  margin: 1rem;
  
  @include respond-above(md) {
    display: flex;
    flex-direction: row;
  }
  
  @include respond-below(sm) {
    padding: 1rem;
  }
  
  @include respond-between(sm, lg) {
    border-radius: 8px;
  }
}

高级响应式混合宏

网格系统混合宏

scss
// 创建响应式网格系统
$grid-columns: 12;
$grid-gutter: 30px;

@mixin make-row($gutter: $grid-gutter) {
  display: flex;
  flex-wrap: wrap;
  margin-right: -($gutter / 2);
  margin-left: -($gutter / 2);
}

@mixin make-col-ready($gutter: $grid-gutter) {
  position: relative;
  width: 100%;
  padding-right: $gutter / 2;
  padding-left: $gutter / 2;
}

@mixin make-col($size, $columns: $grid-columns) {
  flex: 0 0 percentage($size / $columns);
  max-width: percentage($size / $columns);
}

// 响应式网格类生成
.container {
  width: 100%;
  padding-right: 15px;
  padding-left: 15px;
  margin-right: auto;
  margin-left: auto;
}

.row {
  @include make-row;
}

// 生成基础网格类
@for $i from 1 through $grid-columns {
  .col-#{$i} {
    @include make-col-ready;
    @include make-col($i);
  }
}

// 生成响应式网格类
@each $breakpoint, $value in $breakpoints {
  @if $breakpoint != 'xs' {
    @media (min-width: $value) {
      @for $i from 1 through $grid-columns {
        .col-#{$breakpoint}-#{$i} {
          @include make-col-ready;
          @include make-col($i);
        }
        
        @if $i == $grid-columns {
          .col-#{$breakpoint}-auto {
            @include make-col-ready;
            flex: 0 0 auto;
            width: auto;
            max-width: 100%;
          }
          
          .col-#{$breakpoint}-12 {
            @include make-col(12);
          }
        }
      }
      
      // 自动宽度列
      .col-#{$breakpoint}-auto {
        @include make-col-ready;
        flex: 0 0 auto;
        width: auto;
        max-width: 100%;
      }
      
      // 等宽列
      .col-#{$breakpoint} {
        @include make-col-ready;
        flex-basis: 0;
        flex-grow: 1;
        max-width: 100%;
      }
    }
  }
}

Flexbox响应式混合宏

scss
// Flexbox响应式布局混合宏
@mixin flex-container($direction: row, $wrap: nowrap, $align: stretch) {
  display: flex;
  flex-direction: $direction;
  flex-wrap: $wrap;
  align-items: $align;
}

@mixin flex-item($grow: 0, $shrink: 1, $basis: auto) {
  flex: $grow $shrink $basis;
}

@mixin flex-order($order: 0) {
  order: $order;
}

// 响应式Flexbox布局
.flex-layout {
  @include flex-container(row, wrap, center);
  
  .sidebar {
    @include flex-item(0, 0, 250px);
    
    @include respond-to(md) {
      @include flex-item(0, 0, 300px);
    }
    
    @include respond-to(lg) {
      @include flex-item(0, 0, 350px);
    }
  }
  
  .main-content {
    @include flex-item(1, 1, 0);
    min-width: 0; // 防止溢出
    
    @include respond-to(sm) {
      @include flex-item(1, 1, 70%);
    }
  }
}

// 响应式导航
.nav-responsive {
  @include flex-container(row, wrap, center);
  
  .nav-item {
    @include respond-below(md) {
      flex: 1 1 auto;
      text-align: center;
    }
    
    @include respond-above(md) {
      flex: 0 0 auto;
    }
  }
  
  .nav-toggle {
    display: none;
    
    @include respond-below(md) {
      display: block;
      margin-left: auto;
    }
  }
}

响应式工具类

间距工具类

scss
// 间距变量
$spacers: (
  0: 0,
  1: 0.25rem,
  2: 0.5rem,
  3: 1rem,
  4: 1.5rem,
  5: 3rem,
  auto: auto
);

// 生成响应式间距工具类
@each $spacer, $size in $spacers {
  // 基础间距类
  .m-#{$spacer} { margin: $size; }
  .mt-#{$spacer} { margin-top: $size; }
  .mr-#{$spacer} { margin-right: $size; }
  .mb-#{$spacer} { margin-bottom: $size; }
  .ml-#{$spacer} { margin-left: $size; }
  .mx-#{$spacer} { 
    margin-left: $size; 
    margin-right: $size; 
  }
  .my-#{$spacer} { 
    margin-top: $size; 
    margin-bottom: $size; 
  }
  .p-#{$spacer} { padding: $size; }
  .pt-#{$spacer} { padding-top: $size; }
  .pr-#{$spacer} { padding-right: $size; }
  .pb-#{$spacer} { padding-bottom: $size; }
  .pl-#{$spacer} { padding-left: $size; }
  .px-#{$spacer} { 
    padding-left: $size; 
    padding-right: $size; 
  }
  .py-#{$spacer} { 
    padding-top: $size; 
    padding-bottom: $size; 
  }
}

// 响应式间距工具类
@each $breakpoint, $value in $breakpoints {
  @if $breakpoint != 'xs' {
    @media (min-width: $value) {
      @each $spacer, $size in $spacers {
        .m-#{$breakpoint}-#{$spacer} { margin: $size; }
        .mt-#{$breakpoint}-#{$spacer} { margin-top: $size; }
        .me-#{$breakpoint}-#{$spacer} { margin-right: $size; }
        .mb-#{$breakpoint}-#{$spacer} { margin-bottom: $size; }
        .ms-#{$breakpoint}-#{$spacer} { margin-left: $size; }
        .mx-#{$breakpoint}-#{$spacer} { 
          margin-left: $size; 
          margin-right: $size; 
        }
        .my-#{$breakpoint}-#{$spacer} { 
          margin-top: $size; 
          margin-bottom: $size; 
        }
        .p-#{$breakpoint}-#{$spacer} { padding: $size; }
        .pt-#{$breakpoint}-#{$spacer} { padding-top: $size; }
        .pe-#{$breakpoint}-#{$spacer} { padding-right: $size; }
        .pb-#{$breakpoint}-#{$spacer} { padding-bottom: $size; }
        .ps-#{$breakpoint}-#{$spacer} { padding-left: $size; }
        .px-#{$breakpoint}-#{$spacer} { 
          padding-left: $size; 
          padding-right: $size; 
        }
        .py-#{$breakpoint}-#{$spacer} { 
          padding-top: $size; 
          padding-bottom: $size; 
        }
      }
    }
  }
}

显示和隐藏工具类

scss
// 响应式显示/隐藏工具类
@each $breakpoint, $value in $breakpoints {
  @if $breakpoint != 'xs' {
    // 在指定断点及以上显示
    .d-#{$breakpoint}-block { 
      @media (min-width: $value) { 
        display: block !important; 
      } 
    }
    .d-#{$breakpoint}-inline { 
      @media (min-width: $value) { 
        display: inline !important; 
      } 
    }
    .d-#{$breakpoint}-inline-block { 
      @media (min-width: $value) { 
        display: inline-block !important; 
      } 
    }
    .d-#{$breakpoint}-flex { 
      @media (min-width: $value) { 
        display: flex !important; 
      } 
    }
    .d-#{$breakpoint}-grid { 
      @media (min-width: $value) { 
        display: grid !important; 
      } 
    }
    
    // 在指定断点及以上隐藏
    .d-#{$breakpoint}-none { 
      @media (min-width: $value) { 
        display: none !important; 
      } 
    }
  }
}

// 响应式浮动工具类
@each $breakpoint, $value in $breakpoints {
  @if $breakpoint != 'xs' {
    .float-#{$breakpoint}-left { 
      @media (min-width: $value) { 
        float: left !important; 
      } 
    }
    .float-#{$breakpoint}-right { 
      @media (min-width: $value) { 
        float: right !important; 
      } 
    }
    .float-#{$breakpoint}-none { 
      @media (min-width: $value) { 
        float: none !important; 
      } 
    }
  }
}

// 响应式文本对齐工具类
@each $breakpoint, $value in $breakpoints {
  @if $breakpoint != 'xs' {
    .text-#{$breakpoint}-left { 
      @media (min-width: $value) { 
        text-align: left !important; 
      } 
    }
    .text-#{$breakpoint}-center { 
      @media (min-width: $value) { 
        text-align: center !important; 
      } 
    }
    .text-#{$breakpoint}-right { 
      @media (min-width: $value) { 
        text-align: right !important; 
      } 
    }
  }
}

响应式组件

响应式卡片组件

scss
// 响应式卡片组件
.card {
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  word-wrap: break-word;
  background-color: #fff;
  background-clip: border-box;
  border: 1px solid rgba(0, 0, 0, 0.125);
  border-radius: 0.25rem;
  
  // 移动端垂直堆叠
  .card-body {
    flex: 1 1 auto;
    padding: 1.25rem;
  }
  
  .card-header {
    padding: 0.75rem 1.25rem;
    margin-bottom: 0;
    background-color: rgba(0, 0, 0, 0.03);
    border-bottom: 1px solid rgba(0, 0, 0, 0.125);
  }
  
  .card-footer {
    padding: 0.75rem 1.25rem;
    background-color: rgba(0, 0, 0, 0.03);
    border-top: 1px solid rgba(0, 0, 0, 0.125);
  }
  
  .card-img-top {
    width: 100%;
    border-top-left-radius: calc(0.25rem - 1px);
    border-top-right-radius: calc(0.25rem - 1px);
  }
  
  // 响应式行为
  @include respond-to(md) {
    flex-direction: row;
    
    .card-img-top {
      flex: 0 0 auto;
      width: 200px;
      border-top-right-radius: 0;
      border-bottom-left-radius: calc(0.25rem - 1px);
    }
    
    .card-body {
      flex: 1 1 auto;
    }
  }
  
  @include respond-to(lg) {
    .card-img-top {
      width: 250px;
    }
  }
}

// 响应式卡片组
.card-group {
  display: flex;
  flex-direction: column;
  
  @include respond-to(md) {
    flex-flow: row wrap;
    
    .card {
      flex: 1 0 0%;
      
      &:not(:first-child) {
        .card-img-top,
        .card-header {
          border-top-left-radius: 0;
        }
      }
      
      &:not(:last-child) {
        .card-img-bottom,
        .card-footer {
          border-bottom-right-radius: 0;
        }
      }
    }
  }
}

响应式导航组件

scss
// 响应式导航组件
.navbar {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  padding: 0.5rem 1rem;
  
  .navbar-brand {
    display: inline-block;
    padding-top: 0.3125rem;
    padding-bottom: 0.3125rem;
    margin-right: 1rem;
    font-size: 1.25rem;
    line-height: inherit;
    white-space: nowrap;
  }
  
  .navbar-nav {
    display: flex;
    flex-direction: column;
    padding-left: 0;
    margin-bottom: 0;
    list-style: none;
    
    .nav-link {
      display: block;
      padding: 0.5rem 1rem;
      
      &:hover {
        background-color: rgba(0, 0, 0, 0.05);
      }
    }
  }
  
  .navbar-toggler {
    padding: 0.25rem 0.75rem;
    font-size: 1.25rem;
    line-height: 1;
    background-color: transparent;
    border: 1px solid transparent;
    border-radius: 0.25rem;
    
    &:focus {
      outline: 0;
    }
  }
  
  // 移动端折叠菜单
  .navbar-collapse {
    flex-basis: 100%;
    flex-grow: 1;
    align-items: center;
    
    @include respond-to(md) {
      flex-basis: auto;
    }
  }
  
  // 桌面端水平布局
  @include respond-to(md) {
    flex-wrap: nowrap;
    justify-content: flex-start;
    
    .navbar-nav {
      flex-direction: row;
      
      .nav-link {
        padding-right: 0.5rem;
        padding-left: 0.5rem;
      }
    }
    
    .navbar-collapse {
      display: flex !important;
    }
    
    .navbar-toggler {
      display: none;
    }
  }
}

响应式图像和媒体

响应式图像混合宏

scss
// 响应式图像混合宏
@mixin responsive-image($display: block) {
  display: $display;
  max-width: 100%;
  height: auto;
}

// 响应式图片容器
.img-fluid {
  @include responsive-image;
}

.figure {
  display: inline-block;
  
  .img-fluid {
    @include responsive-image;
  }
}

// 响应式背景图像
@mixin responsive-bg-image($image-url, $size: cover, $position: center, $repeat: no-repeat) {
  background-image: url($image-url);
  background-size: $size;
  background-position: $position;
  background-repeat: $repeat;
}

.hero-section {
  height: 100vh;
  @include responsive-bg-image('../images/hero-bg.jpg');
  
  @include respond-to(md) {
    height: 75vh;
  }
  
  @include respond-to(lg) {
    height: 60vh;
  }
}

// 图片懒加载占位符
@mixin image-placeholder($aspect-ratio: 16/9) {
  position: relative;
  width: 100%;
  padding-bottom: percentage(1 / $aspect-ratio);
  
  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: #f8f9fa;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  
  img {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
}

.image-container {
  @include image-placeholder(16/9);
}

高级响应式技术

流体排版

scss
// 流体排版混合宏
@mixin fluid-type($min-vw, $max-vw, $min-font-size, $max-font-size) {
  $slope: ($max-font-size - $min-font-size) / ($max-vw - $min-vw);
  $y-intercept: $min-font-size - $slope * $min-vw;
  $u1: unit($min-vw);
  $u2: unit($max-font-size);

  font-size: calc(#{$y-intercept}#{$u2} + #{strip-units($slope) * 100}vw);
  
  @media (max-width: #{$min-vw}) {
    font-size: $min-font-size;
  }
  
  @media (min-width: #{$max-vw}) {
    font-size: $max-font-size;
  }
}

// 移除单位函数
@function strip-units($number) {
  @return $number / ($number * 0 + 1);
}

// 使用流体排版
.fluid-heading {
  @include fluid-type(320px, 1200px, 1.5rem, 3rem);
}

.fluid-paragraph {
  @include fluid-type(320px, 1200px, 1rem, 1.25rem);
}

// 流体间距
@mixin fluid-spacing($property, $min-vw, $max-vw, $min-size, $max-size) {
  #{$property}: calc(#{$min-size} + #{strip-units($max-size - $min-size)} * ((100vw - #{$min-vw}) / #{strip-units($max-vw - $min-vw)}));
  
  @media (max-width: #{$min-vw}) {
    #{$property}: $min-size;
  }
  
  @media (min-width: #{$max-vw}) {
    #{$property}: $max-size;
  }
}

.fluid-container {
  @include fluid-spacing(padding, 320px, 1200px, 1rem, 3rem);
  @include fluid-spacing(margin, 320px, 1200px, 0.5rem, 2rem);
}

容器查询(实验性)

scss
// 模拟容器查询的混合宏
@mixin container-query($min-width, $max-width: null) {
  @if $max-width {
    @container (min-width: #{$min-width}) and (max-width: #{$max-width}) {
      @content;
    }
  } @else {
    @container (min-width: #{$min-width}) {
      @content;
    }
  }
}

// 响应式组件
.card-container {
  container-type: inline-size;
  
  .card {
    padding: 1rem;
    
    @include respond-to(md) {
      padding: 1.5rem;
    }
    
    @include respond-to(lg) {
      padding: 2rem;
    }
  }
}

实际应用示例

电商产品网格

scss
// 响应式产品网格
.product-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1rem;
  
  @include respond-to(sm) {
    grid-template-columns: repeat(2, 1fr);
  }
  
  @include respond-to(md) {
    grid-template-columns: repeat(3, 1fr);
  }
  
  @include respond-to(lg) {
    grid-template-columns: repeat(4, 1fr);
  }
  
  @include respond-to(xl) {
    grid-template-columns: repeat(5, 1fr);
  }
  
  .product-card {
    display: flex;
    flex-direction: column;
    border: 1px solid #ddd;
    border-radius: 8px;
    overflow: hidden;
    transition: transform 0.3s ease, box-shadow 0.3s ease;
    
    &:hover {
      transform: translateY(-5px);
      box-shadow: 0 10px 20px rgba(0,0,0,0.1);
    }
    
    .product-image {
      width: 100%;
      aspect-ratio: 1/1;
      object-fit: cover;
    }
    
    .product-info {
      padding: 1rem;
      flex-grow: 1;
      display: flex;
      flex-direction: column;
      
      .product-title {
        font-weight: bold;
        margin-bottom: 0.5rem;
      }
      
      .product-price {
        font-weight: bold;
        color: #007bff;
        margin-top: auto;
      }
    }
  }
}

仪表板布局

scss
// 响应式仪表板布局
.dashboard {
  display: grid;
  grid-template-areas: 
    "sidebar main"
    "footer footer";
  grid-template-columns: 250px 1fr;
  grid-template-rows: 1fr auto;
  min-height: 100vh;
  
  @include respond-to(md) {
    grid-template-columns: 200px 1fr;
  }
  
  @include respond-to(lg) {
    grid-template-columns: 250px 1fr;
  }
  
  @include respond-below(md) {
    grid-template-areas: 
      "main"
      "sidebar"
      "footer";
    grid-template-columns: 1fr;
  }
  
  .dashboard-sidebar {
    grid-area: sidebar;
    background-color: #f8f9fa;
    padding: 1rem;
    
    @include respond-below(md) {
      grid-row: 2;
    }
  }
  
  .dashboard-main {
    grid-area: main;
    padding: 1rem;
  }
  
  .dashboard-footer {
    grid-area: footer;
    background-color: #e9ecef;
    padding: 1rem;
    text-align: center;
  }
}

// 响应式仪表板小组件
.widget-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
  
  @include respond-to(sm) {
    grid-template-columns: repeat(2, 1fr);
  }
  
  @include respond-to(lg) {
    grid-template-columns: repeat(3, 1fr);
  }
  
  .widget {
    background: white;
    border-radius: 8px;
    padding: 1.5rem;
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  }
}

Sass为响应式设计提供了强大而灵活的工具,通过合理的使用变量、混合宏和函数,可以创建出既美观又实用的响应式界面。