跳到主要内容

CSS 响应式设计

响应式设计使网页能够适配不同尺寸的设备,从手机到桌面显示器。随着 CSS 的发展,现代响应式设计已经从单纯的媒体查询扩展到容器查询、数学函数等更强大的工具。

响应式设计基础

什么是响应式设计?

响应式设计是一种让网页布局根据设备屏幕尺寸自动调整的方法:

  • 移动优先:从小屏幕开始设计
  • 流式布局:使用相对单位而非固定像素
  • 媒体查询:根据屏幕条件应用不同样式
  • 容器查询:根据容器尺寸调整样式(现代方法)

viewport 视口

<!-- 必须设置! -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">

视口属性说明:

属性说明
width视口宽度,device-width 表示设备宽度
initial-scale初始缩放比例
minimum-scale最小缩放比例
maximum-scale最大缩放比例
user-scalable是否允许用户缩放

媒体查询 @media

媒体查询是响应式设计的基础,它允许我们根据视口大小、设备特性等条件应用不同的样式。

基本语法

/* 屏幕宽度小于768px时生效 */
@media (max-width: 768px) {
body {
font-size: 14px;
}
}

媒体类型

/* screen: 屏幕设备 */
@media screen { }

/* print: 打印机 */
@media print {
.navbar, .sidebar { display: none; }
}

/* all: 所有设备(默认) */
@media all { }

媒体条件

/* 最小宽度 */
@media (min-width: 768px) { }

/* 最大宽度 */
@media (max-width: 768px) { }

/* 宽度范围 */
@media (min-width: 768px) and (max-width: 1024px) { }

/* 屏幕方向 */
@media (orientation: portrait) { } /* 竖屏 */
@media (orientation: landscape) { } /* 横屏 */

/* 设备像素比(高清屏) */
@media (-webkit-min-device-pixel-ratio: 2) { }
@media (resolution: 2dppx) { }

/* 悬停能力 */
@media (hover: hover) { } /* 支持悬停的设备 */
@media (hover: none) { } /* 不支持悬停的设备(触摸屏) */

/* 指针精度 */
@media (pointer: fine) { } /* 精确指针(鼠标) */
@media (pointer: coarse) { } /* 粗略指针(触摸) */

/* 用户偏好 */
@media (prefers-color-scheme: dark) { } /* 深色模式 */
@media (prefers-color-scheme: light) { } /* 浅色模式 */
@media (prefers-reduced-motion: reduce) { } /* 减少动画 */

常用断点

断点的选择应该基于内容需要变化的时机,而不是特定设备。以下是常用的参考断点:

/* 移动端(小屏手机) */
@media (max-width: 575px) { }

/* 平板竖屏(大屏手机) */
@media (min-width: 576px) and (max-width: 767px) { }

/* 平板横屏 */
@media (min-width: 768px) and (max-width: 991px) { }

/* 桌面端 */
@media (min-width: 992px) and (max-width: 1199px) { }

/* 大桌面 */
@media (min-width: 1200px) { }
断点选择建议
  • 优先使用 min-width(移动优先)
  • 断点应该根据内容调整,而非设备
  • 避免过多断点,保持简洁

容器查询 @container

容器查询是 CSS 的现代特性,它允许元素根据其容器的大小而非视口大小来调整样式。这使得组件更加独立和可复用——无论组件被放在页面的哪个位置,都能自动适应其容器。

容器查询与媒体查询的区别

┌─────────────────────────────────────────────────────────────────┐
│ 媒体查询 │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 视口(Viewport) │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ 组件 A │ │ 组件 B │ │ 组件 C │ │ │
│ │ │ 相同样式 │ │ 相同样式 │ │ 相同样式 │ ← 同时响应 │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 容器查询 │
│ ┌──────────────┐ ┌────────────────────┐ │
│ │ 容器 1 │ │ 容器 2 │ │
│ │ ┌─────────┐ │ 宽度 300px │ ┌──────────────┐ │ │
│ │ │ 组件 A │ │ → 紧凑布局 │ │ 组件 B │ │ │
│ │ │ 紧凑布局 │ │ │ │ 宽松布局 │ │ │
│ │ └─────────┘ │ │ └──────────────┘ │ │
│ └──────────────┘ └────────────────────┘ │
│ 宽度 500px → 宽松布局 │
└─────────────────────────────────────────────────────────────────┘

创建容器上下文

要使用容器查询,首先需要声明一个容器上下文:

/* 使用 container-type 属性 */
.sidebar {
container-type: inline-size;
}

container-type 的值:

说明
size基于容器的行内和块尺寸查询
inline-size基于容器的行内尺寸查询(最常用)
normal默认值,不作为尺寸查询容器
推荐使用 inline-size

大多数情况下使用 inline-size 即可,它基于容器的宽度(在水平书写模式下)进行查询,性能开销更小。

基本用法

<div class="sidebar">
<article class="card">
<img src="photo.jpg" alt="">
<div class="card-content">
<h3>标题</h3>
<p>内容描述...</p>
</div>
</article>
</div>
/* 声明容器 */
.sidebar {
container-type: inline-size;
}

/* 默认样式:紧凑布局 */
.card {
display: flex;
flex-direction: column;
padding: 1rem;
}

.card img {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}

/* 容器宽度大于 400px 时切换布局 */
@container (min-width: 400px) {
.card {
flex-direction: row;
gap: 1rem;
}

.card img {
width: 200px;
aspect-ratio: 1;
}
}

命名容器

当页面有多个容器时,可以为容器命名以便精确查询:

/* 使用 container 简写属性 */
.sidebar {
container: sidebar / inline-size;
}

.main-content {
container: main / inline-size;
}

/* 查询特定容器 */
@container sidebar (min-width: 300px) {
.widget { /* 样式 */ }
}

@container main (min-width: 600px) {
.article { /* 样式 */ }
}

也可以分别使用 container-namecontainer-type

.sidebar {
container-name: sidebar;
container-type: inline-size;
}

容器查询单位

容器查询提供了专门的长度单位,相对于容器尺寸计算:

单位说明
cqw容器宽度的 1%
cqh容器高度的 1%
cqi容器行内尺寸的 1%(常用)
cqb容器块尺寸的 1%
cqmincqicqb 中较小的值
cqmaxcqicqb 中较大的值
.card-title {
/* 字体大小根据容器宽度变化 */
font-size: clamp(1rem, 5cqi, 2rem);
}

.card {
/* 内边距相对于容器 */
padding: 2cqi;
}
注意

如果没有可用的查询容器,容器查询单位会回退到小视口单位(svwsvh 等)。

容器查询支持的特性

容器查询支持多种尺寸特性:

/* 宽度查询 */
@container (min-width: 400px) { }

/* 高度查询 */
@container (min-height: 300px) { }

/* 行内尺寸 */
@container (inline-size > 500px) { }

/* 块尺寸 */
@container (block-size > 400px) { }

/* 宽高比 */
@container (aspect-ratio > 16/9) { }

/* 方向 */
@container (orientation: landscape) { }

/* 组合条件 */
@container (min-width: 400px) and (orientation: landscape) { }

完整示例:响应式卡片组件

<!-- 卡片可以在不同容器中自适应 -->
<div class="layout">
<aside class="sidebar">
<article class="card">
<img src="photo.jpg" alt="">
<div class="card-body">
<h3>文章标题</h3>
<p>文章简介...</p>
</div>
</article>
</aside>

<main class="content">
<article class="card">
<img src="photo.jpg" alt="">
<div class="card-body">
<h3>文章标题</h3>
<p>文章简介...</p>
</div>
</article>
</main>
</div>
/* 声明容器 */
.sidebar {
container: sidebar / inline-size;
width: 280px;
}

.content {
container: main / inline-size;
flex: 1;
}

/* 卡片基础样式 */
.card {
display: flex;
flex-direction: column;
background: white;
border-radius: 8px;
overflow: hidden;
transition: transform 0.2s;
}

.card img {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}

.card-body {
padding: 1rem;
}

/* 窄容器:垂直布局 */
@container (max-width: 399px) {
.card {
font-size: 0.9rem;
}

.card-body {
padding: 0.75rem;
}
}

/* 中等容器:水平布局 */
@container (min-width: 400px) {
.card {
flex-direction: row;
}

.card img {
width: 180px;
aspect-ratio: 1;
}

.card-body {
display: flex;
flex-direction: column;
justify-content: center;
}
}

/* 宽容器:更大图片 */
@container main (min-width: 600px) {
.card img {
width: 250px;
}

.card-body {
padding: 1.5rem;
}
}

嵌套容器查询

容器查询可以嵌套,实现更精细的控制:

@container (min-width: 800px) {
.parent {
/* 外层容器样式 */

@container (min-width: 400px) {
.child {
/* 内层容器样式 */
}
}
}
}

容器查询 vs 媒体查询选择指南

场景推荐使用
整体页面布局媒体查询
固定位置的元素(导航栏、页脚)媒体查询
可复用组件容器查询
组件放在不同容器中容器查询
需要响应视口变化媒体查询
需要响应容器变化容器查询
/* 页面级别:媒体查询 */
@media (min-width: 768px) {
.page-layout {
display: grid;
grid-template-columns: 250px 1fr;
}
}

/* 组件级别:容器查询 */
.card-container {
container-type: inline-size;
}

@container (min-width: 400px) {
.card {
flex-direction: row;
}
}

现代响应式函数

CSS 提供了 clamp()min()max() 函数,可以在不使用媒体查询的情况下实现响应式效果。

clamp() 函数

clamp() 函数在最小值和最大值之间选择一个中间值,非常适合响应式字体、间距等。

/* 语法:clamp(最小值, 首选值, 最大值) */
.element {
/* 字体大小在 1rem 到 2rem 之间,优先使用 2.5vw */
font-size: clamp(1rem, 2.5vw, 2rem);
}

工作原理:

  • 如果首选值小于最小值,使用最小值
  • 如果首选值大于最大值,使用最大值
  • 否则使用首选值

常用场景:

/* 响应式标题 */
h1 {
font-size: clamp(1.5rem, 5vw, 3rem);
}

/* 响应式容器宽度 */
.container {
width: clamp(300px, 80%, 1200px);
margin: 0 auto;
}

/* 响应式间距 */
.section {
padding: clamp(1rem, 5vw, 3rem);
}

/* 响应式行高 */
p {
line-height: clamp(1.4, 1.5 + 0.5vw, 1.8);
}

min() 函数

min() 函数返回参数列表中的最小值:

/* 取 50% 和 400px 中较小的那个 */
.sidebar {
width: min(50%, 400px);
/* 当容器小于 800px 时,宽度为 50% */
/* 当容器大于 800px 时,宽度固定为 400px */
}

/* 响应式字体上限 */
.title {
font-size: min(5vw, 48px);
}

/* 响应式间距上限 */
.content {
padding: min(10vw, 80px);
}

max() 函数

max() 函数返回参数列表中的最大值:

/* 取 300px 和 50% 中较大的那个 */
.main {
width: max(300px, 50%);
/* 当容器小于 600px 时,宽度固定为 300px */
/* 当容器大于 600px 时,宽度为 50% */
}

/* 最小字体保证可读性 */
.text {
font-size: max(16px, 1.2vw);
}

/* 最小间距 */
.section {
padding: max(1rem, 5vw);
}

函数对比

函数作用典型场景
min()选择最小值设置最大边界
max()选择最大值设置最小边界
clamp()限制范围同时设置最小、首选、最大值
/* 三者等价关系 */
/* clamp(min, val, max) 等价于 max(min, min(val, max)) */

/* 使用 clamp(推荐,更直观) */
font-size: clamp(1rem, 2.5vw, 2rem);

/* 使用 min/max 组合 */
font-size: max(1rem, min(2.5vw, 2rem));

响应式布局策略

1. 移动优先 vs 桌面优先

移动优先(推荐)

从小屏幕开始设计,逐步添加大屏幕样式:

/* 默认:移动端样式 */
.container {
width: 100%;
padding: 0 15px;
}

/* 平板及以上 */
@media (min-width: 768px) {
.container {
max-width: 750px;
margin: 0 auto;
}
}

/* 桌面及以上 */
@media (min-width: 992px) {
.container { max-width: 970px; }
}

/* 大桌面 */
@media (min-width: 1200px) {
.container { max-width: 1170px; }
}

桌面优先

从大屏幕开始设计,逐步添加小屏幕样式:

/* 默认:桌面样式 */
.container {
max-width: 1170px;
margin: 0 auto;
}

/* 平板及以下 */
@media (max-width: 991px) {
.container { max-width: 750px; }
}

/* 手机及以下 */
@media (max-width: 767px) {
.container { width: 100%; }
}
为什么推荐移动优先
  • 移动端样式通常更简单,作为基础更合理
  • 添加样式比覆盖样式更高效
  • 强制考虑性能和内容优先级
  • CSS 文件更小

2. 流式网格

使用相对单位和 CSS Grid 创建自适应网格:

.container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 0 15px;
}

/* 自动填充网格 */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
}

/* 或使用 auto-fit(更紧凑) */
.grid-compact {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
}

auto-fill vs auto-fit:

  • auto-fill:保留空列,网格项不会拉伸
  • auto-fit:空列会塌陷,网格项会拉伸填满

3. Flexbox 响应式

.nav {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
gap: 20px;
}

.nav-links {
display: flex;
gap: 20px;
}

/* 移动端:垂直排列 */
@media (max-width: 767px) {
.nav {
flex-direction: column;
align-items: stretch;
}

.nav-links {
flex-direction: column;
gap: 10px;
}
}

/* 使用 gap 替代 margin */
.grid {
display: flex;
flex-wrap: wrap;
gap: 20px;
}

.grid-item {
flex: 1 1 calc(50% - 10px); /* 两列布局 */
}

@media (min-width: 768px) {
.grid-item {
flex: 1 1 calc(33.333% - 14px); /* 三列布局 */
}
}

4. 图片响应式

/* 基础:图片自适应 */
img {
max-width: 100%;
height: auto;
display: block;
}

/* 使用 srcset 提供多分辨率 */
<img src="small.jpg"
srcset="small.jpg 500w, medium.jpg 1000w, large.jpg 1500w"
sizes="(max-width: 600px) 100vw, 50vw"
alt="描述">

/* 使用 picture 元素切换图片 */
<picture>
<source media="(max-width: 600px)" srcset="mobile.jpg">
<source media="(min-width: 601px)" srcset="desktop.jpg">
<img src="default.jpg" alt="描述">
</picture>

/* 使用 picture 切换格式 */
<picture>
<source type="image/avif" srcset="image.avif">
<source type="image/webp" srcset="image.webp">
<img src="image.jpg" alt="描述">
</picture>

/* 使用 aspect-ratio 保持比例 */
.card-image {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}

5. 条件显示

/* 仅桌面显示 */
.desktop-only {
display: none;
}

/* 仅移动端显示 */
.mobile-only {
display: block;
}

@media (min-width: 768px) {
.desktop-only { display: block; }
.mobile-only { display: none; }
}

/* 使用视觉隐藏(保留可访问性) */
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}

响应式组件

1. 响应式导航栏

/* 默认:移动端(汉堡菜单) */
.navbar {
display: flex;
flex-direction: column;
padding: 10px;
}

.menu-toggle {
display: block;
align-self: flex-end;
}

.nav-links {
display: none;
flex-direction: column;
}

/* 激活菜单 */
.nav-links.active {
display: flex;
}

/* 平板及以上 */
@media (min-width: 768px) {
.navbar {
flex-direction: row;
justify-content: space-between;
align-items: center;
}

.menu-toggle {
display: none;
}

.nav-links {
display: flex;
flex-direction: row;
gap: 20px;
}
}

2. 响应式卡片

.card-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
}

.card {
display: flex;
flex-direction: column;
padding: 20px;
}

.card img {
width: 100%;
height: 200px;
object-fit: cover;
}

@media (max-width: 575px) {
.card {
padding: 15px;
}
.card img {
height: 150px;
}
}

3. 响应式表单

.form-row {
display: flex;
gap: 20px;
margin-bottom: 15px;
}

.form-group {
flex: 1;
}

@media (max-width: 575px) {
.form-row {
flex-direction: column;
gap: 0;
}
}

4. 响应式表格

/* 横向滚动 */
.table-container {
overflow-x: auto;
}

/* 卡片式转换 */
@media (max-width: 767px) {
table, thead, tbody, th, td, tr {
display: block;
}

thead { display: none; }

tr {
margin-bottom: 15px;
border: 1px solid #ddd;
}

td {
padding: 10px 10px 10px 40%;
position: relative;
}

td::before {
content: attr(data-label);
position: absolute;
left: 10px;
font-weight: bold;
}
}
<table>
<thead>
<tr>
<th>姓名</th>
<th>年龄</th>
<th>城市</th>
</tr>
</thead>
<tbody>
<tr>
<td data-label="姓名">张三</td>
<td data-label="年龄">25</td>
<td data-label="城市">北京</td>
</tr>
</tbody>
</table>

响应式字体

1. 使用相对单位

html { font-size: 16px; }

body { font-size: 1rem; } /* 16px */
h1 { font-size: 2rem; } /* 32px */
p { font-size: 1rem; } /* 16px */

2. CSS clamp()

/* 响应式字体大小:最小14px,最大24px,优先18px */
h1 {
font-size: clamp(1.75rem, 2.5vw, 3rem);
}

p {
font-size: clamp(0.875rem, 1.5vw, 1.125rem);
}

3. 媒体查询调整

html { font-size: 14px; }

@media (min-width: 768px) {
html { font-size: 16px; }
}

@media (min-width: 1200px) {
html { font-size: 18px; }
}

响应式间距

/* 基础间距 */
.section {
padding: 20px;
}

/* 响应式间距 */
@media (min-width: 768px) {
.section { padding: 40px; }
}

@media (min-width: 1200px) {
.section { padding: 60px 0; }
}

/* 使用 CSS 变量 */
:root {
--spacing-sm: 10px;
--spacing-md: 20px;
--spacing-lg: 40px;
}

@media (min-width: 768px) {
:root {
--spacing-sm: 15px;
--spacing-md: 30px;
--spacing-lg: 60px;
}
}

常用响应式模式

1. 侧边栏布局

.layout {
display: grid;
grid-template-columns: 250px 1fr;
}

@media (max-width: 767px) {
.layout {
grid-template-columns: 1fr;
}
.sidebar { display: none; }
}

2. 卡片网格

.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}

@media (max-width: 991px) {
.grid { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 575px) {
.grid { grid-template-columns: 1fr; }
}

3. 堆叠到并排

.content {
display: flex;
flex-direction: column;
}

@media (min-width: 768px) {
.content {
flex-direction: row;
}
.main { flex: 2; }
.sidebar { flex: 1; }
}

测试工具

浏览器开发者工具

  1. 按 F12 打开开发者工具
  2. 点击设备模拟图标(手机图标)
  3. 选择不同设备或自定义尺寸

在线工具

小结

本章学习了:

  1. 响应式基础:viewport 设置、移动优先理念
  2. 媒体查询:条件语法、常用断点、用户偏好检测
  3. 容器查询:创建容器上下文、命名容器、容器查询单位
  4. 现代响应式函数:clamp()、min()、max() 实现无媒体查询的响应式
  5. 布局策略:流式网格、Flexbox 响应式、图片响应式
  6. 响应式组件:导航、卡片、表单、表格
  7. 响应式内容:图片、字体、间距

练习

  1. 使用容器查询创建一个可在不同容器中自适应的卡片组件
  2. 使用 clamp() 实现响应式字体大小(最小 1rem,最大 2rem)
  3. 创建一套响应式网格系统,使用 Grid 的 auto-fill/auto-fit
  4. 实现一个响应式导航栏,包含移动端汉堡菜单
  5. 将表格转换为移动端卡片布局
  6. 对比媒体查询和容器查询的使用场景,各创建一个示例
  7. 使用 min() 和 max() 函数实现侧边栏的响应式宽度