CSS 响应式设计:从媒体查询到容器查询

2024-08-28 前端

引言

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 屏幕的实际影响:

  1. 图片 — 1x 图片在 2x 屏幕上会模糊,需要 srcset 提供多倍图
  2. 边框 — 1px 边框在 3x 屏幕上实际占 3 个物理像素,视觉上不够「细」
  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、speech
  • media-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) {
  /* 有至少一个触屏指点器(如混合设备) */
}

用户偏好类

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 插件自动转译。

除了在 CSS 文件中写 @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

经典的两栏流式布局:

使用 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="响应式图片"
/>

浏览器逻辑:

  1. 根据当前视口宽度和 sizes 计算出图片应占多少像素
  2. 从 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 属性优雅降级:

性能优化小贴士:

<!-- 添加 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 实战:组件级响应式卡片

下面是一个完整的、完全基于容器查询的响应式卡片组件:

容器查询最大的价值在于:组件不再依赖于它被放置的位置。同一个卡片组件放在侧边栏、主内容区、弹性网格中都能自动适配。

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 作为字体单位时需注意:

  1. 极小视口下字体过小:font-size: 4vw 在 320px 宽的手机上 = 12.8px,太小。必须配合 clamp() 设置下限。

  2. 极大视口下字体过大:font-size: 4vw 在 3840px 宽的屏幕上 = 153px,太大。必须配合 clamp() 设置上限。

  3. 横竖屏切换时不要仅依赖 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);
}

完整的响应式排版系统:


7. 响应式导航模式

导航是响应式设计中最具挑战性的部分之一。以下是三种常用的响应式导航方案。

7.1 CSS Only 汉堡菜单

利用 :target 伪类或隐藏 checkbox 实现无需 JavaScript 的菜单切换:

7.2 导航折叠(带 JS 增强)

CSS Only 的方案在可访问性和键盘操作方面有局限。生产环境中建议用少量 JavaScript 增强:

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. 综合实战:完整的响应式页面

以下是一个融合了本文所有技术的综合示例:


总结

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+

推荐阅读


本文发布于 2024-08-28,技术内容已按现代浏览器的最新支持情况更新。