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