引言
2010 年,Ethan Marcotte 在 A List Apart 上发表了那篇著名的文章 Responsive Web Design,正式将「响应式设计」这个概念带入主流前端开发视野。十四年后的今天,设备尺寸从智能手表横跨到 8K 显示器,响应式设计早已不是可选项,而是 Web 开发的必修课。
本文将系统梳理 CSS 响应式设计的完整技术栈——从最基础的 viewport 配置、媒体查询,到现代的容器查询、流式排版和响应式导航模式,每个知识点都配有可直接运行的代码示例。
1. 响应式设计基础
1.1 viewport meta 标签
在移动端浏览器普及之前,大部分网页是为桌面显示器设计的(通常宽度为 960px-1024px)。当这些页面在手机上打开时,浏览器会尝试将整个页面缩小到手机屏幕宽度,导致文字极小、用户需要手动缩放。
viewport meta 标签告诉浏览器如何控制页面尺寸和缩放比例。标准写法:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>响应式页面基础</title>
</head>
<body>
<p>这是一个响应式页面</p>
</body>
</html>各属性含义:
| 属性 | 可选值 | 说明 |
|---|---|---|
width |
device-width 或具体像素值 |
视口宽度,推荐使用 device-width |
height |
device-height 或具体像素值 |
视口高度,通常不设置 |
initial-scale |
0.1 - 10.0 |
页面初始缩放比例,推荐 1.0 |
minimum-scale |
0.1 - 10.0 |
最小缩放比例 |
maximum-scale |
0.1 - 10.0 |
最大缩放比例 |
user-scalable |
yes / no |
是否允许用户缩放(出于可访问性,不建议设为 no) |
⚠️ 不要忘记
width=device-width。如果只写initial-scale=1.0而不指定宽度,部分浏览器会默认使用桌面宽度的视口。
1.2 物理像素 vs 逻辑像素 vs CSS 像素
这是响应式设计最基础也最容易被忽略的概念。
| 概念 | 说明 | 例子 |
|---|---|---|
| 物理像素 (Physical Pixel) | 屏幕上的实际发光点 | iPhone 15 Pro Max 屏幕有 2796 × 1290 个物理像素 |
| 逻辑像素 / 设备独立像素 (DIP) | 操作系统用来布局的抽象像素 | iPhone 的 point,Android 的 dp |
| CSS 像素 | CSS 中 px 单位对应的像素,通常等于逻辑像素 |
width: 100px 中的 100px |
三者关系:
CSS 像素 × devicePixelRatio ≈ 物理像素在一个标准 Retina 屏幕上:
- 一个 CSS 像素对应 2×2 或 3×3 个物理像素
width: 200px的盒子在 3x 屏幕上实际占据 600 个物理像素的宽度
1.3 devicePixelRatio 与 Retina 屏幕
window.devicePixelRatio 表示物理像素与 CSS 像素的比率:
// 在浏览器控制台查看
console.log(`Device Pixel Ratio: ${window.devicePixelRatio}`);常见值:
| 设备 | devicePixelRatio | 代表机型 |
|---|---|---|
| 普通桌面显示器 | 1 | 老旧显示器、外接 1080p 显示器 |
| Retina 笔记本 | 2 | MacBook Pro (2012-2021) |
| 高分显示器 | 1.5-2 | 4K 显示器缩放至 200% |
| 高端手机 | 3 | iPhone X/Pro 系列、三星 Galaxy S 系列 |
| 部分 Android | 1.5-4 | 视厂商配置 |
Retina 屏幕的实际影响:
- 图片 — 1x 图片在 2x 屏幕上会模糊,需要
srcset提供多倍图 - 边框 —
1px边框在 3x 屏幕上实际占 3 个物理像素,视觉上不够「细」 - Canvas — 需要按
devicePixelRatio缩放绘制尺寸
/* 通过媒体查询检测高分屏 */
@media (-webkit-min-device-pixel-ratio: 2),
(min-resolution: 192dpi) {
.retina-element {
background-image: url('icon@2x.png');
}
}1.4 移动端优先 vs 桌面端优先策略
两种策略的核心区别在于 CSS 断点的编写方式。
移动端优先 (Mobile First)
先写移动端(最小屏幕)的样式,用 min-width 向上增强:
/* 基准:移动端 (< 768px) */
.container {
display: flex;
flex-direction: column;
padding: 16px;
}
/* 平板 (≥ 768px) */
@media (min-width: 768px) {
.container {
flex-direction: row;
padding: 24px;
}
}
/* 桌面 (≥ 1200px) */
@media (min-width: 1200px) {
.container {
max-width: 1200px;
margin-inline: auto;
}
}优点:默认样式轻量,符合移动端流量占比高的现实;不需要额外的 max-width 重置。
缺点:桌面端需要写更多覆盖规则。
桌面端优先 (Desktop First)
先写桌面端完整样式,用 max-width 向下降级:
/* 基准:桌面端 (≥ 1200px) */
.container {
display: flex;
flex-direction: row;
max-width: 1200px;
margin-inline: auto;
}
/* 平板 (< 1200px) */
@media (max-width: 1199.98px) {
.container {
padding: 24px;
}
}
/* 移动端 (< 768px) */
@media (max-width: 767.98px) {
.container {
flex-direction: column;
padding: 16px;
}
}优点:桌面端样式是基准,更符合有些团队的设计流程。
缺点:每个断点都需要用 max-width 缩小覆盖;移动端需要加载多余的桌面样式。
推荐:除非你的主要受众是桌面用户且移动端是次要场景,否则优先选择移动端优先策略。Google 的搜索引擎也优先根据移动端页面进行索引(Mobile-First Indexing)。
2. 媒体查询 (Media Queries)
2.1 基本语法
@media media-type and (media-feature-rule) {
/* 符合条件时应用的样式 */
}media-type:可选项,包括all(默认)、screen、print、speechmedia-feature-rule:一个或多个条件表达式
基础示例:
/* 仅在屏幕上且宽度 ≥ 600px 时生效 */
@media screen and (min-width: 600px) {
body {
background: lightblue;
}
}
/* 打印时隐藏导航和广告 */
@media print {
nav, .ad-banner, .sidebar {
display: none;
}
body {
font-size: 12pt;
}
}2.2 断点策略
「断点」是样式发生变化的屏幕宽度阈值。常见的断点体系:
按设备分类(传统方式)
/* 手机 (< 576px) — 不需要媒体查询,作为基准 */
/* 大屏手机 / 小屏平板 (≥ 576px) */
@media (min-width: 576px) { /* ... */ }
/* 平板 (≥ 768px) */
@media (min-width: 768px) { /* ... */ }
/* 小屏桌面 / 横屏平板 (≥ 992px) */
@media (min-width: 992px) { /* ... */ }
/* 大屏桌面 (≥ 1200px) */
@media (min-width: 1200px) { /* ... */ }
/* 超大屏 (≥ 1400px) */
@media (min-width: 1400px) { /* ... */ }按内容确定断点(推荐方式)
不要照搬设备尺寸,而是根据你的设计内容来定断点:
/* 当导航栏开始拥挤时展开 */
@media (min-width: 680px) {
.nav-links {
display: flex;
}
}
/* 当两栏布局太挤时变成三栏 */
@media (min-width: 960px) {
.grid {
grid-template-columns: repeat(3, 1fr);
}
}核心原则:断点应该跟随内容,而不是设备。
2.3 逻辑操作符
and — 所有条件必须同时满足
/* 横屏且宽度 ≥ 600px */
@media (min-width: 600px) and (orientation: landscape) {
.sidebar {
width: 300px;
}
}or(逗号 ,)— 任一条件满足即可
/* 宽度 ≤ 480px 或横屏模式(任一满足) */
@media (max-width: 480px), (orientation: landscape) {
.menu {
font-size: 14px;
}
}not — 否定一个条件
/* 非打印设备 */
@media not print {
.screen-only {
display: block;
}
}only — 防止老浏览器解析
/* 仅支持媒体查询的浏览器才会应用 */
@media only screen and (min-width: 768px) {
/* ... */
}
only的用途是让不支持媒体查询的旧 IE 忽略整个规则集。在当今浏览器中已基本不需要,但可作为防御性编码。
2.4 媒体特性详解
尺寸类
/* 宽度 */
@media (width >= 768px) { /* ... */ }
@media (max-width: 768px) { /* ... */ } /* 传统写法 */
@media (width < 400px) { /* ... */ }
/* 高度 */
@media (min-height: 800px) {
.hero {
height: 100vh;
}
}
/* 宽高比 */
@media (aspect-ratio: 16/9) {
.video-container {
aspect-ratio: 16 / 9;
}
}
@media (min-aspect-ratio: 4/3) { /* ... */ }显示类
/* 横屏 / 竖屏 */
@media (orientation: portrait) {
/* 宽度 < 高度 */
}
@media (orientation: landscape) {
/* 宽度 > 高度 */
}
/* 分辨率 */
@media (min-resolution: 192dpi) {
/* 高分屏 */
}
@media (min-resolution: 2dppx) {
/* 同上,更直观的写法 */
}交互类
/* 是否有悬停能力 */
@media (hover: hover) {
.card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}
}
@media (hover: none) {
/* 触屏设备 — 悬停无意义 */
.card:active {
transform: scale(0.98);
}
}
/* 指点精度 */
@media (pointer: fine) {
/* 鼠标、触控板等精确指点器 */
}
@media (pointer: coarse) {
/* 手指触摸 — 需要更大的点击区域 */
.button {
min-height: 44px;
min-width: 44px;
}
}
@media (any-pointer: coarse) {
/* 有至少一个触屏指点器(如混合设备) */
}用户偏好类
/* 深色模式 */
@media (prefers-color-scheme: dark) {
:root {
--bg: #1a1a2e;
--text: #e0e0e0;
--link: #66b3ff;
}
}
/* 浅色模式(默认) */
@media (prefers-color-scheme: light) {
:root {
--bg: #ffffff;
--text: #222222;
--link: #0066cc;
}
}
/* 减少动画 */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
/* 减少透明 */
@media (prefers-reduced-transparency: reduce) {
.glass-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: none;
}
}
/* 高对比度 */
@media (prefers-contrast: more) {
.badge {
border: 2px solid currentColor;
}
}2.5 Range 语法 (Level 4)
CSS Media Queries Level 4 引入了更直观的 range 语法,用数学比较运算符代替 min- / max- 前缀:
/* 传统写法 */
@media (min-width: 768px) and (max-width: 1199px) { /* ... */ }
/* 新 range 语法 */
@media (768px <= width < 1200px) { /* ... */ }
/* 更多示例 */
@media (width >= 500px) { /* 宽 ≥ 500px */ }
@media (width < 400px) { /* 宽 < 400px */ }
@media (400px < width < 1200px) { /* 宽在 400-1200px 之间(不含边界) */ }
/* 也适用于 resolution */
@media (150dpi <= resolution <= 300dpi) { /* ... */ }目前所有现代浏览器均已支持 range 语法。如果需要对老 IE 做兼容,可使用 PostCSS 插件自动转译。
2.6 @import 与 <link media>
除了在 CSS 文件中写 @media 规则,还可以在引入样式时指定媒体条件:
<link media=""> 方式
<!-- 仅在打印时加载 -->
<link rel="stylesheet" href="print.css" media="print" />
<!-- 仅在宽度 ≥ 768px 时加载 -->
<link rel="stylesheet" href="desktop.css" media="(min-width: 768px)" />
<!-- 组合条件 -->
<link rel="stylesheet" href="dark.css" media="(prefers-color-scheme: dark)" />@import 方式
/* 在 CSS 文件中根据条件导入 */
@import url('mobile.css') (max-width: 767px);
@import url('desktop.css') (min-width: 768px);
@import url('print.css') print;⚠️
@import会阻塞渲染,且多个@import是串行下载的,性能不如<link>。生产环境中建议用构建工具合并 CSS 中的@media规则,而不是运行时使用@import。
3. Flexible 布局
3.1 CSS 单位体系
响应式布局的核心在于选择合适的 CSS 单位。下面是常用单位的对比:
| 单位 | 相对基准 | 适用场景 |
|---|---|---|
px |
绝对单位 | 边框、阴影、图标尺寸 |
em |
父元素的 font-size |
组件内部的间距、排版 |
rem |
根元素 (html) 的 font-size |
全局间距、排版 |
vw |
视口宽度的 1% | 全宽元素、大标题 |
vh |
视口高度的 1% | 全屏 Hero、视差 |
vmin |
vw 和 vh 中较小的值 |
始终适应较短的边 |
vmax |
vw 和 vh 中较大的值 |
极少使用 |
% |
父元素对应属性值 | 流式布局、栅格 |
cqw |
容器查询容器的宽度 1% | 容器内相对布局 |
rem vs em 实战示例
html {
font-size: 16px; /* 浏览器默认,可显式声明 */
}
/* rem 始终基于 html 的 font-size */
.title {
font-size: 2rem; /* = 32px */
margin-bottom: 1rem; /* = 16px */
}
/* em 基于当前元素的 font-size */
.card {
font-size: 18px;
padding: 1em; /* = 18px */
}
.card .button {
font-size: 0.875em; /* = 15.75px ≈ 16px */
padding: 0.5em 1em; /* 基于按钮自身的 font-size */
}经验法则:用
rem做全局排版和间距,用em做组件内部相对关系,用px做边框和装饰。
vw/vh 实战
/* 全屏 Hero */
.hero {
height: 100vh; /* 高度填满视口 */
display: flex;
align-items: center;
justify-content: center;
}
/* 响应式字体大小(见第 6 章详解) */
.hero-title {
font-size: calc(1.5rem + 3vw);
}
/* 基于视口的间距 */
.section {
padding: min(5vw, 60px);
}3.2 比较函数:clamp()、min()、max()
这三个 CSS 函数是实现流式设计的核心工具。
min() — 取最小值
/* 宽度不超过 1200px,但允许比 1200px 小 */
.container {
width: min(100%, 1200px);
margin-inline: auto;
}
/* 字体不超过 48px */
.title {
font-size: min(5vw, 48px);
}max() — 取最大值
/* 宽度至少 320px */
.sidebar {
width: max(320px, 25%);
}
/* 字体至少 16px */
.text {
font-size: max(1rem, 2.5vw);
}clamp() — 限制区间(最常用)
语法:clamp(MIN, PREFERRED, MAX)
/* 字体在 16px ~ 48px 之间,按 4vw 比例缩放 */
.title {
font-size: clamp(1rem, 4vw, 3rem);
}
/* 宽度在 320px ~ 1200px 之间,按 80% 比例 */
.container {
width: clamp(320px, 80%, 1200px);
}
/* Padding 在 16px ~ 64px 之间 */
.card {
padding: clamp(1rem, 5%, 4rem);
}3.3 流式布局:百分比 + max-width
经典的两栏流式布局:
/* ===== HTML ===== */
/*
<div class="fluid-layout">
<header class="header">Header</header>
<main class="main">
<article class="content">主内容</article>
<aside class="sidebar">侧边栏</aside>
</main>
<footer class="footer">Footer</footer>
</div>
*/
/* ===== CSS ===== */
.fluid-layout {
max-width: 1200px;
margin-inline: auto;
padding: 0 1rem;
}
.header {
width: 100%;
padding: 1rem;
}
.main {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
}
.content {
flex: 1 1 65%; /* 占 65% 空间,可收缩可增长 */
min-width: 0; /* 允许 flex 子项收缩到内容宽度以下 */
}
.sidebar {
flex: 1 1 30%; /* 占 30% 空间 */
min-width: 250px; /* 防止侧边栏太窄 */
}
.footer {
width: 100%;
padding: 1rem;
}
/* 移动端:自动堆叠 */
@media (max-width: 768px) {
.main {
flex-direction: column;
}
.content,
.sidebar {
flex-basis: 100%;
}
}使用 Grid 的流式布局更简洁:
.auto-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
}这个 auto-fit / minmax(280px, 1fr) 模式是 CSS Grid 中最强大的响应式技巧之一:每列最小 280px,能放几列放几列,多余空间均匀分配。
4. 响应式图片
4.1 srcset + sizes
srcset 允许浏览器根据设备像素比和视口宽度自动选择最合适的图片资源。
按设备像素比选择 (x 描述符)
<img
src="photo.jpg"
srcset="photo@1x.jpg 1x, photo@2x.jpg 2x, photo@3x.jpg 3x"
alt="响应式示例图片"
/>浏览器逻辑:devicePixelRatio = 2 → 加载 photo@2x.jpg
按视口宽度选择 (w 描述符 + sizes)
<img
src="photo-800.jpg"
srcset="
photo-400.jpg 400w,
photo-800.jpg 800w,
photo-1200.jpg 1200w,
photo-1600.jpg 1600w
"
sizes="
(max-width: 600px) 100vw,
(max-width: 1024px) 50vw,
33vw
"
alt="响应式图片"
/>浏览器逻辑:
- 根据当前视口宽度和
sizes计算出图片应占多少像素 - 从
srcset中选择最接近且不浪费带宽的资源
如果浏览器宽度是 400px,
sizes计算为100vw= 400px,浏览器会选择第一个 ≥400px 且分辨率合适的源(即photo-800.jpg)。
4.2 <picture> + <source media>
当需要根据屏幕条件切换不同的图片格式或裁剪方式时,<picture> 比 srcset 更灵活:
<picture>
<!-- 浏览器从上往下匹配第一个条件 -->
<source
media="(min-width: 1024px)"
srcset="hero-desktop.webp, hero-desktop@2x.webp 2x"
type="image/webp"
/>
<source
media="(min-width: 768px)"
srcset="hero-tablet.webp, hero-tablet@2x.webp 2x"
type="image/webp"
/>
<source
media="(min-width: 1024px)"
srcset="hero-desktop.jpg, hero-desktop@2x.jpg 2x"
type="image/jpeg"
/>
<source
media="(min-width: 768px)"
srcset="hero-tablet.jpg, hero-tablet@2x.jpg 2x"
type="image/jpeg"
/>
<!-- 最后的 <img> 作为 fallback,也提供 srcset -->
<img
src="hero-mobile.jpg"
srcset="hero-mobile@2x.jpg 2x"
alt="页面横幅"
loading="lazy"
/>
</picture>浏览器匹配规则:
<picture>中的<source>从上到下匹配,命中第一个符合条件的即停止。<img>标签始终是最后的 fallback,所有浏览器都需要保留。
4.3 WebP / AVIF 回退
现代图片格式 WebP 和 AVIF 在压缩率上远优于 JPEG/PNG。通过 <picture> + type 属性优雅降级:
<picture>
<!-- AVIF:最佳压缩率(Chrome、Firefox) -->
<source
type="image/avif"
srcset="
photo-400.avif 400w,
photo-800.avif 800w,
photo-1200.avif 1200w
"
sizes="(max-width: 600px) 100vw, 50vw"
/>
<!-- WebP:兼容性好(Chrome、Firefox、Edge、Safari 14+) -->
<source
type="image/webp"
srcset="
photo-400.webp 400w,
photo-800.webp 800w,
photo-1200.webp 1200w
"
sizes="(max-width: 600px) 100vw, 50vw"
/>
<!-- JPEG fallback -->
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
sizes="(max-width: 600px) 100vw, 50vw"
alt="摄影作品"
loading="lazy"
decoding="async"
/>
</picture>性能优化小贴士:
<!-- 添加 loading 和 decoding 属性 -->
<img
src="photo.jpg"
loading="lazy" <!-- 懒加载 -->
decoding="async" <!-- 异步解码,不阻塞渲染 -->
fetchpriority="high" <!-- 首屏图片提高优先级 -->
alt="..."
/>5. 容器查询 (Container Queries)
容器查询是 CSS 近年来最重要的新增特性之一。它允许组件根据自身容器的尺寸(而非视口)来调整样式,彻底解决了组件复用时的「视口依赖」问题。
5.1 问题背景
传统的媒体查询基于视口,但同一个组件可能出现在不同尺寸的容器中:
/* ❌ 媒体查询无法区分组件所在的环境 */
@media (min-width: 768px) {
.card {
flex-direction: row; /* 在大屏上应该是行布局 */
}
}如果同一个 .card 组件同时出现在侧边栏(窄)和主内容区(宽),媒体查询无法分别处理。
5.2 container-type 与 container-name
要让一个元素成为容器查询的上下文,需要设置 container-type:
/* 成为查询容器 */
.card-container {
container-type: inline-size; /* 监听内联方向的尺寸变化 */
/* 等价于 */
container: inline-size / container-name;
}container-type 值 |
说明 |
|---|---|
inline-size |
仅监听内联尺寸(推荐,触发重排较少) |
size |
监听内联和块两个方向尺寸 |
normal |
不是查询容器 |
命名容器:
.sidebar-wrapper {
container: sidebar / inline-size;
}
.main-wrapper {
container: main / inline-size;
}简写语法:
/* container: <name> / <type> */
.wrapper {
container: card / inline-size;
}
/* 仅 type */
.wrapper {
container-type: inline-size;
}
/* 仅 name */
.wrapper {
container-name: sidebar;
}
/* 同时 */
.wrapper {
container: sidebar / inline-size;
}5.3 @container 语法
.card-container {
container: card / inline-size;
}
/* 当容器宽度 ≥ 400px 时 */
@container (min-width: 400px) {
.card {
flex-direction: row;
}
.card-image {
width: 40%;
}
.card-content {
width: 60%;
}
}
/* 当容器宽度 ≥ 600px 时 */
@container (min-width: 600px) {
.card {
padding: 2rem;
}
}
/* 命名容器查询 */
@container card (min-width: 500px) {
.card-title {
font-size: clamp(1.2rem, 2cqi + 1rem, 2rem);
}
}容器查询中可使用
cqw(容器宽度 1%)、cqi(容器内联尺寸 1%)等单位。
5.4 实战:组件级响应式卡片
<!-- HTML -->
<div class="demo-grid">
<div class="card-wrapper narrow">
<article class="card">
<img class="card-image" src="thumb.jpg" alt="缩略图" />
<div class="card-body">
<h3 class="card-title">容器查询卡片</h3>
<p class="card-desc">这个卡片的布局完全取决于容器宽度,而非视口大小。</p>
<a class="card-cta" href="#">了解更多 →</a>
</div>
</article>
</div>
<div class="card-wrapper wide">
<article class="card">
<img class="card-image" src="thumb.jpg" alt="缩略图" />
<div class="card-body">
<h3 class="card-title">同一组件,不同容器</h3>
<p class="card-desc">即使在同一个页面中,左右两个卡片容器宽度不同,样式也会自动适配。</p>
<a class="card-cta" href="#">了解更多 →</a>
</div>
</article>
</div>
<div class="card-wrapper full">
<article class="card">
<img class="card-image" src="thumb.jpg" alt="缩略图" />
<div class="card-body">
<h3 class="card-title">全宽容器中的卡片</h3>
<p class="card-desc">这是全宽容器中的卡片,它将展示大尺寸容器的布局。</p>
<a class="card-cta" href="#">了解更多 →</a>
</div>
</article>
</div>
</div>/* CSS — 无需任何媒体查询,完全依赖容器查询 */
/* 定义容器上下文 */
.card-wrapper {
container: card / inline-size;
}
/* 基础样式 — 窄容器默认(< 350px 时,卡片堆叠) */
.card {
display: flex;
flex-direction: column;
border-radius: 12px;
overflow: hidden;
background: #fff;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
transition: box-shadow 0.2s;
}
.card-image {
width: 100%;
height: 200px;
object-fit: cover;
}
.card-body {
padding: 1rem;
}
.card-title {
font-size: 1.25rem;
margin: 0 0 0.5rem;
}
.card-desc {
font-size: 0.875rem;
color: #555;
margin: 0 0 1rem;
}
.card-cta {
display: inline-block;
padding: 0.5rem 1rem;
background: #0066cc;
color: #fff;
border-radius: 6px;
text-decoration: none;
}
/* 容器宽度 ≥ 350px:图片和文字并排 */
@container card (min-width: 350px) {
.card {
flex-direction: row;
align-items: stretch;
}
.card-image {
width: 150px;
height: auto;
flex-shrink: 0;
}
.card-body {
flex: 1;
}
}
/* 容器宽度 ≥ 500px:更大图片、更大字体 */
@container card (min-width: 500px) {
.card-image {
width: 220px;
}
.card-title {
font-size: 1.5rem;
}
.card-desc {
font-size: 1rem;
}
.card-body {
padding: 1.5rem;
}
}
/* 容器宽度 ≥ 700px:全尺寸卡片 */
@container card (min-width: 700px) {
.card {
border-radius: 16px;
box-shadow: 0 4px 16px rgba(0,0,0,0.1);
}
.card-image {
width: 300px;
}
.card-body {
padding: 2rem;
}
.card-title {
font-size: 1.75rem;
}
}
/* ===== 容器宽度对照 ===== */
/* 通过外部 Grid 控制容器宽度,卡片内部完全不用关心视口 */
.demo-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 1.5rem;
}
@media (max-width: 900px) {
.demo-grid {
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 600px) {
.demo-grid {
grid-template-columns: 1fr;
}
}容器查询最大的价值在于:组件不再依赖于它被放置的位置。同一个卡片组件放在侧边栏、主内容区、弹性网格中都能自动适配。
5.5 容器查询 vs 媒体查询对比
| 特性 | 媒体查询 | 容器查询 |
|---|---|---|
| 参考对象 | 视口 (viewport) | 父容器 |
| 作用范围 | 全局样式 | 样式作用域内的元素 |
| 典型场景 | 页面布局断点 | 组件级别的自适应 |
| 复用性 | 组件换位置可能需要重写 | 组件在任何容器中自动适配 |
| 浏览器兼容 | 全兼容(IE 9+) | Chrome 105+, Firefox 110+, Safari 16+ |
| 性能 | 无额外开销 | 额外开销很小 |
何时用容器查询? 当你开发可复用的 UI 组件(卡片、面板、列表项、表单等),且它们可能出现在不同宽度的父容器中时,容器查询是最优雅的解决方案。
6. 响应式排版
6.1 流式排版:vw + clamp()
理想的响应式排版应该是「连续缩放」,而非在若干断点间跳跃。
基础实现
/* 在断点处跳跃的「非连续」排版——不推荐 */
h1 {
font-size: 1.5rem; /* 移动端 */
}
@media (min-width: 768px) {
h1 { font-size: 2rem; }
}
@media (min-width: 1200px) {
h1 { font-size: 3rem; }
}/* ✅ 连续缩放的流式排版 */
h1 {
font-size: clamp(1.5rem, 1.5rem + 2vw, 3rem);
}更精确的流式排版公式
如果需要精确控制字体在某个视口范围内的缩放行为,可以用一个通用公式:
font-size = preferred + rate × (viewport - breakpoint)或者直接用 clamp() 结合 calc:
/* 视口 375px 时 = 16px,视口 1200px 时 = 24px */
/* 斜率 = (24 - 16) / (1200 - 375) = 8 / 825 ≈ 0.0097vw */
/* 基准 = 16 - 0.0097 × 375 = 12.36px */
body {
font-size: clamp(1rem, 0.7725rem + 0.97vw, 1.5rem);
}
/* 等价于:16px → (1200 - 375) = 8px 增量 */为了便于管理,推荐用 CSS 自定义属性:
:root {
/* 排版级别 */
--text-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
--text-sm: clamp(0.875rem, 0.8rem + 0.35vw, 1rem);
--text-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
--text-lg: clamp(1.125rem, 1rem + 0.6vw, 1.375rem);
--text-xl: clamp(1.25rem, 1rem + 1.2vw, 1.75rem);
--text-2xl: clamp(1.5rem, 1rem + 2.5vw, 2.5rem);
--text-3xl: clamp(2rem, 1rem + 4vw, 3.5rem);
--text-4xl: clamp(2.5rem, 1rem + 6vw, 4.5rem);
}
/* 使用 */
h1 { font-size: var(--text-4xl); }
h2 { font-size: var(--text-2xl); }
h3 { font-size: var(--text-xl); }
p { font-size: var(--text-base); }
small { font-size: var(--text-sm); }6.2 视口单位字体的陷阱
使用 vw 作为字体单位时需注意:
-
极小视口下字体过小:
font-size: 4vw在 320px 宽的手机上 = 12.8px,太小。必须配合clamp()设置下限。 -
极大视口下字体过大:
font-size: 4vw在 3840px 宽的屏幕上 = 153px,太大。必须配合clamp()设置上限。 -
横竖屏切换时不要仅依赖 vw:考虑
vi(内联方向)或vmin。
/* ❌ 危险:无限制的 vw */
h1 { font-size: 6vw; } /* 手机上 19px,4K 屏上 230px */
/* ✅ 安全:clamp 设限 */
h1 { font-size: clamp(1.5rem, 6vw, 4rem); }6.3 行高与字间距的响应式调整
行高和字间距也需要随屏幕尺寸变化以保持可读性:
:root {
/* 行高:大屏上行文更宽松 */
--line-height-tight: 1.3;
--line-height-normal: 1.6;
--line-height-relaxed: 1.8;
}
body {
font-size: var(--text-base);
line-height: var(--line-height-normal);
}
/* 大篇幅文本在宽屏上需要更宽松的行高 */
article {
line-height: clamp(1.5, 1.5 + 0.5vw, 1.8);
}
/* 标题在宽屏上增加字符间距 */
h1 {
letter-spacing: clamp(-0.02em, -0.01em + 0.1vw, 0.05em);
}
/* 正文段落:窄屏上字间距为 0,宽屏上略微增加 */
p {
letter-spacing: clamp(0em, -0.01em + 0.05vw, 0.03em);
}完整的响应式排版系统:
/* ===== 响应式排版系统 ===== */
:root {
/* 基础字体 */
--font-sans: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
--font-serif: 'Georgia', 'Times New Roman', serif;
/* 流式尺寸等级 */
--fs--2: clamp(0.625rem, 0.6rem + 0.1vw, 0.75rem);
--fs--1: clamp(0.75rem, 0.7rem + 0.2vw, 0.875rem);
--fs-0: clamp(1rem, 0.9rem + 0.4vw, 1.125rem);
--fs-1: clamp(1.125rem, 1rem + 0.6vw, 1.375rem);
--fs-2: clamp(1.25rem, 1rem + 1.2vw, 1.75rem);
--fs-3: clamp(1.5rem, 1rem + 2.5vw, 2.5rem);
--fs-4: clamp(2rem, 1rem + 4vw, 3.5rem);
--fs-5: clamp(2.5rem, 1rem + 6vw, 4.5rem);
/* 行高 */
--lh-body: clamp(1.5, 1.5 + 0.3vw, 1.8);
--lh-heading: 1.1;
}
body {
font-family: var(--font-sans);
font-size: var(--fs-0);
line-height: var(--lh-body);
}
h1, h2, h3 { line-height: var(--lh-heading); }
h1 { font-size: var(--fs-5); }
h2 { font-size: var(--fs-3); }
h3 { font-size: var(--fs-2); }
h4 { font-size: var(--fs-1); }
small, .text-small { font-size: var(--fs--1); }7. 响应式导航模式
导航是响应式设计中最具挑战性的部分之一。以下是三种常用的响应式导航方案。
7.1 CSS Only 汉堡菜单
利用 :target 伪类或隐藏 checkbox 实现无需 JavaScript 的菜单切换:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CSS Only 汉堡菜单</title>
<style>
/* ===== 重置 ===== */
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: system-ui, sans-serif;
}
/* ===== 导航栏 ===== */
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
background: #1a1a2e;
color: #fff;
}
.logo {
font-size: 1.5rem;
font-weight: bold;
}
/* ===== 汉堡菜单按钮 ===== */
.menu-toggle {
display: none; /* 隐藏 checkbox */
}
.menu-btn {
display: none; /* 大屏隐藏汉堡按钮 */
cursor: pointer;
background: none;
border: none;
padding: 0.5rem;
z-index: 10;
}
.menu-btn span {
display: block;
width: 28px;
height: 3px;
background: #fff;
margin: 5px 0;
border-radius: 2px;
transition: transform 0.3s, opacity 0.3s;
}
/* ===== 导航链接 ===== */
.nav-links {
display: flex;
list-style: none;
gap: 1.5rem;
}
.nav-links a {
color: #fff;
text-decoration: none;
padding: 0.5rem 0;
transition: opacity 0.2s;
}
.nav-links a:hover {
opacity: 0.7;
}
/* ===== 移动端:≤ 768px ===== */
@media (max-width: 768px) {
.menu-btn {
display: block; /* 显示汉堡按钮 */
}
.nav-links {
display: none; /* 默认隐藏菜单 */
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgba(26, 26, 46, 0.98);
gap: 2rem;
font-size: 1.5rem;
}
/* checkbox 选中时显示菜单 */
.menu-toggle:checked ~ .nav-links {
display: flex;
}
/* 汉堡按钮动画:变为 X */
.menu-toggle:checked ~ .menu-btn span:nth-child(1) {
transform: rotate(45deg) translate(5px, 6px);
}
.menu-toggle:checked ~ .menu-btn span:nth-child(2) {
opacity: 0;
}
.menu-toggle:checked ~ .menu-btn span:nth-child(3) {
transform: rotate(-45deg) translate(5px, -6px);
}
}
/* ===== 大屏展示完整导航 ===== */
@media (min-width: 769px) {
.nav-links {
display: flex !important;
}
}
</style>
</head>
<body>
<nav class="navbar">
<div class="logo">Logo</div>
<!-- hidden checkbox 控制菜单显示 -->
<input type="checkbox" id="menu-toggle" class="menu-toggle" />
<!-- 汉堡按钮(label 关联 checkbox) -->
<label for="menu-toggle" class="menu-btn" aria-label="切换菜单">
<span></span>
<span></span>
<span></span>
</label>
<!-- 导航链接 -->
<ul class="nav-links">
<li><a href="#">首页</a></li>
<li><a href="#">产品</a></li>
<li><a href="#">关于</a></li>
<li><a href="#">博客</a></li>
<li><a href="#">联系</a></li>
</ul>
</nav>
<main style="padding: 2rem; max-width: 800px; margin-inline: auto;">
<h1>CSS Only 汉堡菜单</h1>
<p>这个菜单完全不需要 JavaScript,仅用 CSS 的 :checked 和 ~ 兄弟选择器实现。</p>
</main>
</body>
</html>7.2 导航折叠(带 JS 增强)
CSS Only 的方案在可访问性和键盘操作方面有局限。生产环境中建议用少量 JavaScript 增强:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>JS 增强响应式导航</title>
<style>
/* ===== 基础样式 ===== */
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: system-ui, sans-serif; }
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 2rem;
background: #0f172a;
color: #fff;
position: relative;
}
.logo { font-size: 1.5rem; font-weight: bold; }
/* ===== 汉堡按钮 ===== */
.hamburger {
display: none;
flex-direction: column;
gap: 5px;
background: none;
border: none;
cursor: pointer;
padding: 4px;
z-index: 100;
}
.hamburger .bar {
display: block;
width: 28px;
height: 3px;
background: #fff;
border-radius: 2px;
transition: transform 0.3s, opacity 0.3s;
}
.hamburger.active .bar:nth-child(1) {
transform: rotate(45deg) translate(5px, 6px);
}
.hamburger.active .bar:nth-child(2) {
opacity: 0;
}
.hamburger.active .bar:nth-child(3) {
transform: rotate(-45deg) translate(5px, -6px);
}
/* ===== 导航菜单 ===== */
.nav-menu {
display: flex;
list-style: none;
gap: 1.5rem;
align-items: center;
}
.nav-menu a {
color: #e2e8f0;
text-decoration: none;
padding: 0.5rem 0;
transition: color 0.2s;
font-size: 0.95rem;
}
.nav-menu a:hover { color: #60a5fa; }
/* ===== 下拉菜单 ===== */
.dropdown {
position: relative;
}
.dropdown-toggle {
display: flex;
align-items: center;
gap: 4px;
cursor: pointer;
}
.dropdown-toggle::after {
content: '▾';
font-size: 0.75rem;
transition: transform 0.2s;
}
.dropdown-toggle.open::after {
transform: rotate(180deg);
}
.dropdown-menu {
display: none;
position: absolute;
top: 100%;
left: 0;
background: #1e293b;
border-radius: 8px;
padding: 0.5rem 0;
min-width: 160px;
box-shadow: 0 4px 16px rgba(0,0,0,0.3);
z-index: 50;
}
.dropdown-menu li {
list-style: none;
}
.dropdown-menu a {
display: block;
padding: 0.5rem 1rem;
white-space: nowrap;
}
.dropdown-menu a:hover {
background: #334155;
}
.dropdown.open .dropdown-menu {
display: block;
}
/* ===== 移动端适配 ===== */
@media (max-width: 768px) {
.hamburger {
display: flex;
}
.nav-menu {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgba(15, 23, 42, 0.98);
gap: 2rem;
font-size: 1.25rem;
}
.nav-menu.open {
display: flex;
}
/* 移动端下拉改为点击展开,不再悬停 */
.dropdown-menu {
position: static;
background: transparent;
box-shadow: none;
padding: 0.5rem 0 0;
text-align: center;
min-width: auto;
}
.dropdown-menu a {
padding: 0.5rem 2rem;
}
.dropdown-menu a:hover {
background: transparent;
color: #60a5fa;
}
}
</style>
</head>
<body>
<nav class="navbar" role="navigation" aria-label="主导航">
<div class="logo">MyApp</div>
<button class="hamburger" id="hamburger" aria-label="打开菜单" aria-expanded="false">
<span class="bar"></span>
<span class="bar"></span>
<span class="bar"></span>
</button>
<ul class="nav-menu" id="navMenu" role="menubar">
<li><a href="#" role="menuitem">首页</a></li>
<li><a href="#" role="menuitem">产品</a></li>
<!-- 多级下拉菜单 -->
<li class="dropdown" role="none">
<a class="dropdown-toggle" href="#" role="menuitem" aria-haspopup="true" aria-expanded="false">
服务
</a>
<ul class="dropdown-menu" role="menu">
<li><a href="#" role="menuitem">Web 开发</a></li>
<li><a href="#" role="menuitem">移动开发</a></li>
<li><a href="#" role="menuitem">UI/UX 设计</a></li>
<li><a href="#" role="menuitem">咨询</a></li>
</ul>
</li>
<li><a href="#" role="menuitem">关于</a></li>
<li><a href="#" role="menuitem">联系</a></li>
</ul>
</nav>
<main style="padding: 3rem 2rem; max-width: 800px; margin-inline: auto;">
<h1>响应式导航 — JS 增强版</h1>
<p>这个版本支持:汉堡菜单切换、下拉菜单点击展开、ARIA 可访问性属性、键盘 Escape 关闭。</p>
</main>
<script>
(function() {
const hamburger = document.getElementById('hamburger');
const navMenu = document.getElementById('navMenu');
const dropdownToggles = document.querySelectorAll('.dropdown-toggle');
// === 汉堡菜单切换 ===
hamburger.addEventListener('click', () => {
const isOpen = navMenu.classList.toggle('open');
hamburger.classList.toggle('active');
hamburger.setAttribute('aria-expanded', isOpen);
hamburger.setAttribute('aria-label', isOpen ? '关闭菜单' : '打开菜单');
});
// === 下拉菜单切换(点击) ===
dropdownToggles.forEach(toggle => {
toggle.addEventListener('click', (e) => {
e.preventDefault();
const dropdown = toggle.closest('.dropdown');
const isOpen = dropdown.classList.toggle('open');
toggle.setAttribute('aria-expanded', isOpen);
toggle.classList.toggle('open');
});
});
// === 点击外部关闭菜单 ===
document.addEventListener('click', (e) => {
if (!e.target.closest('.navbar')) {
navMenu.classList.remove('open');
hamburger.classList.remove('active');
hamburger.setAttribute('aria-expanded', 'false');
hamburger.setAttribute('aria-label', '打开菜单');
}
});
// === Escape 键关闭 ===
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
navMenu.classList.remove('open');
hamburger.classList.remove('active');
hamburger.setAttribute('aria-expanded', 'false');
hamburger.setAttribute('aria-label', '打开菜单');
// 关闭所有下拉
document.querySelectorAll('.dropdown.open').forEach(d => {
d.classList.remove('open');
d.querySelector('.dropdown-toggle').setAttribute('aria-expanded', 'false');
});
}
});
})();
</script>
</body>
</html>7.3 CSS Grid 自适应导航
另一种常见的响应式导航方案是利用 CSS Grid 的 auto-fit 和 minmax 自动适应:
.grid-nav {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 0.5rem;
padding: 1rem;
background: #1a1a2e;
}
.grid-nav a {
text-align: center;
padding: 0.75rem;
color: #fff;
text-decoration: none;
border-radius: 6px;
background: rgba(255,255,255,0.1);
transition: background 0.2s;
}
.grid-nav a:hover {
background: rgba(255,255,255,0.2);
}<nav class="grid-nav">
<a href="#">首页</a>
<a href="#">产品</a>
<a href="#">服务</a>
<a href="#">关于</a>
<a href="#">博客</a>
<a href="#">联系</a>
</nav>当容器宽度足够时,6 个链接排成一行;当宽度不足时,链接自动换行。完全不需要媒体查询。
8. 综合实战:完整的响应式页面
以下是一个融合了本文所有技术的综合示例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>响应式综合示例</title>
<style>
/* ===== 1. 重置与自定义属性 ===== */
*, *::before, *::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
/* 颜色 */
--color-primary: #3b82f6;
--color-bg: #f8fafc;
--color-surface: #ffffff;
--color-text: #1e293b;
--color-text-secondary: #64748b;
/* 流式排版 */
--fs-body: clamp(0.875rem, 0.8rem + 0.35vw, 1rem);
--fs-h1: clamp(1.75rem, 1rem + 4vw, 3rem);
--fs-h2: clamp(1.25rem, 1rem + 1.5vw, 2rem);
--fs-h3: clamp(1rem, 0.9rem + 0.6vw, 1.25rem);
--space-xs: clamp(0.25rem, 0.2rem + 0.25vw, 0.5rem);
--space-sm: clamp(0.5rem, 0.4rem + 0.5vw, 0.75rem);
--space-md: clamp(1rem, 0.8rem + 1vw, 1.5rem);
--space-lg: clamp(1.5rem, 1rem + 2vw, 3rem);
--space-xl: clamp(2rem, 1.5rem + 3vw, 4rem);
}
body {
font-family: system-ui, -apple-system, sans-serif;
font-size: var(--fs-body);
line-height: 1.6;
color: var(--color-text);
background: var(--color-bg);
}
/* ===== 2. 暗色模式 ===== */
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #0f172a;
--color-surface: #1e293b;
--color-text: #e2e8f0;
--color-text-secondary: #94a3b8;
--color-primary: #60a5fa;
}
}
/* ===== 3. 减少动画 ===== */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
/* ===== 4. 容器与布局 ===== */
.container {
width: min(100% - 2rem, 1200px);
margin-inline: auto;
}
/* ===== 5. 导航(CSS Only 汉堡菜单) ===== */
.site-header {
background: var(--color-surface);
border-bottom: 1px solid rgba(0,0,0,0.08);
padding: var(--space-md) 0;
}
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
}
.nav-toggle {
display: none;
}
.nav-btn {
display: none;
cursor: pointer;
background: none;
border: none;
padding: 0.25rem;
}
.nav-btn span {
display: block;
width: 24px;
height: 2px;
background: var(--color-text);
margin: 5px 0;
transition: 0.3s;
}
.nav-links {
display: flex;
list-style: none;
gap: var(--space-lg);
}
.nav-links a {
color: var(--color-text-secondary);
text-decoration: none;
font-weight: 500;
transition: color 0.2s;
}
.nav-links a:hover,
.nav-links a.active {
color: var(--color-primary);
}
@media (max-width: 768px) {
.nav-btn {
display: block;
}
.nav-links {
display: none;
position: fixed;
inset: 0;
flex-direction: column;
align-items: center;
justify-content: center;
background: var(--color-surface);
gap: var(--space-lg);
font-size: 1.25rem;
z-index: 100;
}
.nav-toggle:checked ~ .nav-links {
display: flex;
}
.nav-toggle:checked ~ .nav-btn span:nth-child(1) {
transform: rotate(45deg) translate(5px, 5px);
}
.nav-toggle:checked ~ .nav-btn span:nth-child(2) {
opacity: 0;
}
.nav-toggle:checked ~ .nav-btn span:nth-child(3) {
transform: rotate(-45deg) translate(5px, -5px);
}
}
/* ===== 6. Hero Section ===== */
.hero {
padding: var(--space-xl) 0;
text-align: center;
}
.hero h1 {
font-size: var(--fs-h1);
line-height: 1.1;
margin-bottom: var(--space-md);
}
.hero p {
font-size: var(--fs-h3);
color: var(--color-text-secondary);
max-width: 60ch;
margin-inline: auto;
margin-bottom: var(--space-lg);
}
/* ===== 7. Grid 卡片区(响应式 Grid) ===== */
.features {
padding: var(--space-xl) 0;
}
.features h2 {
font-size: var(--fs-h2);
margin-bottom: var(--space-lg);
text-align: center;
}
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: var(--space-lg);
}
/* ===== 8. 卡片(容器查询) ===== */
.card-wrapper {
container: card / inline-size;
}
.card {
background: var(--color-surface);
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
transition: transform 0.2s, box-shadow 0.2s;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0,0,0,0.1);
}
.card-image {
width: 100%;
height: 200px;
background: linear-gradient(135deg, var(--color-primary), #8b5cf6);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 2rem;
}
.card-body {
padding: var(--space-md);
}
.card-body h3 {
font-size: var(--fs-h3);
margin-bottom: var(--space-xs);
}
.card-body p {
color: var(--color-text-secondary);
margin-bottom: var(--space-sm);
}
.card-tag {
display: inline-block;
padding: 0.2em 0.6em;
background: var(--color-primary);
color: #fff;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 600;
}
/* 容器查询:宽容器时卡片横排 */
@container card (min-width: 450px) {
.card {
display: flex;
align-items: stretch;
}
.card-image {
width: 160px;
height: auto;
flex-shrink: 0;
}
.card-body {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
}
@container card (min-width: 600px) {
.card-image {
width: 220px;
}
.card-body {
padding: var(--space-lg);
}
}
/* ===== 9. 响应式排版示例区 ===== */
.typography-demo {
padding: var(--space-xl) 0;
}
.typography-demo h2 {
font-size: var(--fs-h2);
margin-bottom: var(--space-md);
}
.fluid-text {
font-size: clamp(1rem, 0.5rem + 3vw, 2rem);
background: linear-gradient(135deg, var(--color-primary), #8b5cf6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* ===== 10. Footer ===== */
.site-footer {
background: var(--color-surface);
border-top: 1px solid rgba(0,0,0,0.08);
padding: var(--space-lg) 0;
text-align: center;
color: var(--color-text-secondary);
}
/* ===== 11. 打印样式 ===== */
@media print {
.site-header { display: none; }
.hero { padding: 1rem 0; }
.card { break-inside: avoid; }
}
</style>
</head>
<body>
<!-- 导航 -->
<header class="site-header">
<div class="container navbar">
<div class="logo" style="font-weight: bold; font-size: 1.25rem;">
🚀 RWD Demo
</div>
<input type="checkbox" id="navToggle" class="nav-toggle" />
<label for="navToggle" class="nav-btn" aria-label="切换菜单">
<span></span>
<span></span>
<span></span>
</label>
<ul class="nav-links">
<li><a href="#" class="active">首页</a></li>
<li><a href="#">特性</a></li>
<li><a href="#">文档</a></li>
<li><a href="#">关于</a></li>
</ul>
</div>
</header>
<main>
<!-- Hero -->
<section class="hero container">
<h1>CSS 响应式设计<br />从媒体查询到容器查询</h1>
<p>一个融合了媒体查询、容器查询、流式排版、响应式图片和暗色模式的完整示例页面。</p>
<small style="color: var(--color-text-secondary);">
调整浏览器窗口大小查看效果
</small>
</section>
<!-- 特性卡片 -->
<section class="features container">
<h2>核心特性</h2>
<div class="card-grid">
<div class="card-wrapper">
<article class="card">
<div class="card-image">📱</div>
<div class="card-body">
<h3>媒体查询</h3>
<p>经典的视口断点策略,支持移动端优先的页面级自适应布局。</p>
<span class="card-tag">Media Queries</span>
</div>
</article>
</div>
<div class="card-wrapper">
<article class="card">
<div class="card-image">📦</div>
<div class="card-body">
<h3>容器查询</h3>
<p>组件根据自身容器宽度自适应,不再依赖视口。同一组件在不同位置自动适配。</p>
<span class="card-tag">Container Queries</span>
</div>
</article>
</div>
<div class="card-wrapper">
<article class="card">
<div class="card-image">🔤</div>
<div class="card-body">
<h3>流式排版</h3>
<p>使用 clamp() + vw 实现字体大小的连续缩放,告别跳跃式断点。</p>
<span class="card-tag">Fluid Typography</span>
</div>
</article>
</div>
<div class="card-wrapper">
<article class="card">
<div class="card-image">🌓</div>
<div class="card-body">
<h3>暗色模式</h3>
<p>自动根据系统偏好切换深色/浅色主题,并尊重用户的减少动画设置。</p>
<span class="card-tag">prefers-color-scheme</span>
</div>
</article>
</div>
</div>
</section>
<!-- 排版示例 -->
<section class="typography-demo container">
<h2>流式排版示例</h2>
<p class="fluid-text">
这段文字的大小随视口连续变化:最小 1rem,最大 2rem。
</p>
<p style="margin-top: var(--space-md); color: var(--color-text-secondary);">
调整浏览器宽度,你会看到上述文字平滑地缩放,而不是在某个断点处突然跳跃。
</p>
</section>
</main>
<footer class="site-footer">
<div class="container">
<p>© 2024 响应式设计示例 — 综合展示 CSS 响应式技术栈</p>
</div>
</footer>
</body>
</html>总结
CSS 响应式设计已经从一个「加分项」变成了 Web 开发的「必备技能」。回顾本文的内容,我们可以画出一条清晰的学习路径:
| 阶段 | 技术 | 重点 |
|---|---|---|
| 基础 | viewport、像素概念、策略选择 | 正确配置移动端视口,理解 CSS 像素的含义 |
| 页面对应 | 媒体查询、断点策略 | 使用 min-width 移动端优先编写响应式布局 |
| 弹性布局 | rem/vw/clamp()、flex/grid | 用比较函数实现连续缩放,用 Grid 的 auto-fit 实现自动换行 |
| 资源优化 | srcset、picture、WebP/AVIF | 根据屏幕分辨率加载最合适的图片资源 |
| 组件自治 | 容器查询、cqw 单位 | 组件不再依赖视口,实现真正的容器内自适应 |
| 体验增强 | 流式排版、暗色模式、减少动画 | 尊重用户偏好,提升可读性和可访问性 |
| 导航方案 | CSS only / JS 增强菜单 | 提供良好的移动端导航体验 |
浏览器兼容性速查
| 特性 | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
| 媒体查询 Level 3 | ✅ | ✅ | ✅ | ✅ |
| Range 语法 (L4) | ✅ 104+ | ✅ 109+ | ✅ 16.2+ | ✅ 104+ |
clamp() / min() / max() |
✅ 79+ | ✅ 75+ | ✅ 13.1+ | ✅ 79+ |
| 容器查询 | ✅ 105+ | ✅ 110+ | ✅ 16.2+ | ✅ 105+ |
prefers-color-scheme |
✅ 76+ | ✅ 67+ | ✅ 12.1+ | ✅ 79+ |
prefers-reduced-motion |
✅ 74+ | ✅ 63+ | ✅ 10.1+ | ✅ 79+ |
| WebP | ✅ 23+ | ✅ 65+ | ✅ 14+ | ✅ 18+ |
| AVIF | ✅ 85+ | ✅ 93+ | ✅ 16.1+ | ✅ 121+ |
推荐阅读
- MDN: CSS Container Queries
- MDN: Using Media Queries
- Responsive Web Design - Ethan Marcotte
- Container Queries: a Practical Guide - Ahmad Shadeed
- Utopia.fyi — Fluid Typography Calculator
本文发布于 2024-08-28,技术内容已按现代浏览器的最新支持情况更新。