←返回笔记列表
01 · NOTES / WebGIS 开发

第 08 节 · 响应式与媒体查询

2026 年 9 月 7 日

第 08 节 · 响应式与媒体查询

📌 版本信息:基于 CSS Media Queries Level 4 与移动端 viewport 现行标准(2026-09-06 核对) 📚 来源:MDN 响应式设计 | MDN viewport meta | MDN 媒体查询

当同一套页面要装进三种屏幕

第 07 节之后,桌面端的布局已经完整:顶栏、侧栏、三列卡片、地图主区各就各位。但把同一个页面拿到手机上打开,问题立刻暴露:三列卡片挤成一条线,侧栏占掉半屏,导航菜单被压成细条,整页出现横向滚动条。

这不是“再写一套手机版页面”能解决的,而是要让同一套 HTML/CSS 在不同视口下自动切换形态。地图应用里尤其明显:手机上先看到地图,图层树收起来;平板上卡片两列;桌面上三列加侧栏。响应式的核心不是背设备型号,而是先做一套能伸缩的基础样式,再在布局撑不住的地方补断点。

这一节讲什么

本节讲 viewport meta、媒体查询、相对单位、clamp() 和移动优先的写法。学完这一节可以做到:

  • 解释 viewport meta 为什么是移动端响应式的前置条件
  • 用 min-width 媒体查询完成手机、平板、桌面三档布局
  • 用百分比、vw/vh、clamp() 减少死像素
  • 按“布局在哪一宽度撑不住”确定断点,而不是照抄设备表
  • 处理小屏表格横滚和 iPhone 底部安全区

viewport meta:移动端入场券

每个页面的 <head> 里都要有这一行:

<meta name="viewport" content="width=device-width, initial-scale=1.0" />

没有它时,手机浏览器会把页面当成约 980px 宽的桌面页渲染,再整体缩小塞进屏幕。后果是文字小到看不清、媒体查询全部失灵,因为媒体查询判断的“视口宽度”根本不是设备真实宽度。

width=device-width 让视口宽度等于设备宽度,initial-scale=1.0 让初始缩放比例为 1。这是移动端布局的入场券,写响应式页面第一步就是确认它在。

媒体查询:按视口宽度分发 CSS

/* 基础样式:先写手机优先的默认样式 */
.cards { display: flex; flex-direction: column; gap: 12px; }
.sidebar { display: none; }

/* 平板:视口 ≥768px 时生效 */
@media (min-width: 768px) {
  .cards { flex-direction: row; flex-wrap: wrap; }
}

/* 桌面:视口 ≥1024px 时生效 */
@media (min-width: 1024px) {
  .sidebar { display: block; flex: 0 0 220px; }
}

/* 组合条件:横屏且足够宽 */
@media (min-width: 1024px) and (orientation: landscape) {
  /* … */
}

min-width 是“从小屏往大屏增强”,对应移动优先;max-width 是“从大屏往小屏裁剪”,对应桌面优先。现代项目推荐移动优先,因为手机样式最简单,后面的增强只是加规则,不用推翻默认值。

断点不是设备型号表。640px、768px、1024px 只是常见经验值,真正的做法是拖宽浏览器窗口,观察布局在哪一个宽度开始挤、开始溢出,就在那里加断点。

相对单位:别把宽度写死成像素

单位 含义 典型用途
% 相对父元素 宽度自适应:width: 50%
vw / vh 视口宽/高的 1% 全屏地图 height: 100vh
rem 相对根元素字号(默认 16px) 间距、字号,可整体缩放
em 相对自身字号 行内小调整
/* 死像素:窗口变窄就溢出 */
.sidebar { width: 300px; }

/* 比例思维:有下限也有上限 */
.sidebar { width: 30%; max-width: 320px; min-width: 220px; }
.map { height: calc(100vh - 56px); }  /* 视口高减顶栏 */

clamp() 是响应式字号的一行解法:

h1 { font-size: clamp(20px, 4vw, 32px); }

三个参数依次是最小值、理想值、最大值:窗口窄时不低于 20px,宽时不超过 32px,中间随视口平滑变化。vw 类单位的一大陷阱是 100vh:手机浏览器地址栏收起、展开会改变可视高度,100vh 会跟着跳。现代写法优先 100dvh(dynamic viewport height),地图全屏场景尤其重要。

移动优先:手机样式从哪里开始写

导航栏。 小屏菜单项多,允许换行比硬挤一行更实用:

.navbar {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
}
.navbar .links {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
}

侧栏。 手机上默认隐藏,桌面上再以 Flex 子项身份出现:

.sidebar { display: none; }

@media (min-width: 1024px) {
  .sidebar {
    display: block;
    flex: 0 0 220px;  /* 宽度写进 flex 子项,而不是只写 width */
  }
}

卡片。 手机单列,平板两列,桌面三列:

.cards { display: flex; flex-direction: column; gap: 12px; }

@media (min-width: 768px) {
  .cards { flex-direction: row; flex-wrap: wrap; }
  .card { flex: 0 0 calc(50% - 6px); }
}

@media (min-width: 1024px) {
  .card { flex: 0 0 calc(33.33% - 8px); }
}

表格。 小屏整页横滚很难受,把横滚限制在表格外壳里:

.table-scroll { overflow-x: auto; }
table { min-width: 560px; }  /* 外壳可滚,页面本身不横向溢出 */

底部安全区。 iPhone 底部小黑条会盖住贴底内容,用环境变量补一段 padding:

.page-footer {
  padding-bottom: calc(16px + env(safe-area-inset-bottom));
}

非 iOS 设备上 env(safe-area-inset-bottom) 是 0,所以这段可以放心写,不影响普通桌面浏览器。

响应式三件套

  1. viewport meta(HTML 层):让视口宽度等于设备宽度。
  2. 弹性布局(CSS 层):Flex、百分比、clamp(),让布局天然能伸缩。
  3. 媒体查询(断点层):在布局撑不住的地方补规则。

可沿用一句判断:能用比例和弹性就不写断点,媒体查询是补丁而不是主体。默认样式先保证手机能用,再逐级增强,代码量和心智负担都会小很多。

易错点

  • 少了 viewport meta:媒体查询按 980px 判断,和手机真实宽度脱节,怎么调都像失灵。
  • 基础样式写在媒体查询后面:媒体查询不会提高优先级,后面的普通规则照样覆盖它。把手机优先样式放最前,媒体查询放后面。
  • 断点照抄设备型号:iPhone、iPad 的像素年年变,设备表没有意义。以自己布局开始“破”的宽度为准。
  • 宽度写死成像素:width: 300px 不设上下限,小屏必溢出;用比例加 min-width/max-width。
  • 只给侧栏写 width:侧栏在 Flex 容器里,尺寸要用 flex: 0 0 220px 管,只写 width 可能被 flex-basis 或收缩行为干扰。
  • 100vh 用于全屏地图:手机地址栏变化会让它跳动,全屏场景优先 100dvh。
  • 小屏表格整页横滚:表格内容超宽时页面出现横向滚动条,整体体验很差;用 overflow-x: auto 包一层,把滚动限制在表格内部。

动手练习:08 三端自适应页

配套文件:02-web-basics/examples/08-三端自适应页.html。页面有导航栏、侧栏、标题、六张城市卡片、城市数据表和页脚,正好覆盖手机、平板、桌面三档。文件里有 6 组 TODO(第 4 组分 a/b 两步),用 Live Server 打开后配合 DevTools 设备模拟(Ctrl+Shift+M)观察。

参考答案:

/* TODO 1:贴进 .navbar,导航栏手机优先,放不下就换行 */
	display: flex;
	flex-wrap: wrap;
	gap: 16px;

/* TODO 2:贴进 .sidebar 规则,手机上隐藏侧栏 */
	display: none;

/* TODO 2b:贴进桌面媒体查询里的 .sidebar 规则 */
	display: block;
	flex: 0 0 220px;

/* TODO 3:把 h1 里已有的 font-size: 32px 改成下面这行 */
	font-size: clamp(20px, 4vw, 32px);

/* TODO 4a:贴进平板媒体查询里的 .cards 规则 */
	flex-direction: row;
	flex-wrap: wrap;
/* TODO 4b:贴进平板媒体查询里的 .card 规则 */
	flex: 0 0 calc(50% - 6px);

/* TODO 5:贴进桌面媒体查询里的 .card 规则 */
	flex: 0 0 calc(33.33% - 8px);

/* TODO 6:把 .page-footer 里已有的 padding-bottom: 16px 改成下面这行 */
	padding-bottom: calc(16px + env(safe-area-inset-bottom));

值得停下来理解的地方:

  • TODO 1 同时给 .navbar 和 .links 开 Flex + wrap:外层管 logo 和链接组,内层管链接本身,两层都要能换行。
  • TODO 2b 必须写 flex: 0 0 220px 而不是只写 width: 220px,因为 .sidebar 是 .layout 的 Flex 子项,尺寸由 flex-basis 主导。
  • TODO 4 的 - 6px:两列之间只有一个 12px gap,12 ÷ 2 = 6。TODO 5 的 - 8px:三列之间两个 12px gap,24 ÷ 3 = 8。
  • TODO 6 在普通浏览器里看不出变化,是正常的;只有 iOS 带底部安全区的设备上,页脚才会多出一段留白。

通关标准:

  • 手机宽(约 375px):导航换行不溢出、侧栏隐藏、卡片单列、页面无横向滚动条
  • 平板宽(约 768px):卡片两列
  • 桌面宽(约 1280px):侧栏出现并固定 220px,卡片三列
  • 能说出移动优先和桌面优先的区别,以及为什么默认样式从手机开始写

自测题

  1. 少写 viewport meta,媒体查询在手机上为什么不生效?
  2. min-width: 768px 的媒体查询属于移动优先还是桌面优先?
  3. clamp(20px, 4vw, 32px) 在 500px 宽的屏幕上,字号实际是多少?
  4. 断点应该照抄设备型号表吗?应该怎么定?
  5. height: 100vh 用于全屏地图有什么已知陷阱?

参考答案

  1. 手机浏览器默认把视口当约 980px 宽,媒体查询按 980px 匹配,和真实设备宽度脱节。
  2. 移动优先。默认写手机样式,min-width 从小屏往大屏增强。
  3. 20px。500px × 4% = 20px,正好落在最小值和最大值之间,实际取 20px。
  4. 不照抄。拖宽/拖窄浏览器窗口,观察布局在哪一宽度开始挤或溢出,那个宽度就是断点。
  5. 手机地址栏收起、展开会改变可视高度,100vh 会跳动;全屏场景优先用 100dvh。

下一步

HTML/CSS 阶段一到这里收尾,第 09 节开始进入 JavaScript。在此之前,可以用综合项目 C-02a 个人主页把第 01~08 节连起来:结构、表单、选择器、盒模型、Flex、定位、响应式,做一个三页响应式作品集。

TAGSweb