响应式不是做两套页面
早年做移动端适配,常见做法是单独做一个 m.example.com 手机站,写两套 HTML 两套 CSS,再通过 UA 判断跳转。这种方案的代价是双倍维护:一个 Bug 要改两遍,一次新功能要同步两套代码。响应式设计(Responsive Design)的思路彻底不同——一套 HTML、一套 CSS,通过媒体查询和流式布局让同一个页面在不同宽度下自动调整布局。
它的核心思想可以概括成三句话:流式网格(元素用相对单位而不是固定像素)、弹性图片(图片永远不超过容器宽度)、媒体查询(在不同断点下调整布局)。三者配合,页面就像水一样,倒进什么形状的容器就变成什么形状。
响应式的判断标准:把浏览器窗口从最窄拖到最宽,页面应该全程没有横向滚动条,内容始终可读、布局始终合理。如果中间某个宽度突然崩了,那就是断点没设对。
- 移动优先先写 375px 下的样式,再用 min-width 媒体查询扩展到宽屏。
- 断点选在布局真的需要变的地方,而不是按设备型号硬写。
- clamp一行声明让字号、间距在最小值和最大值之间随视口流式缩放。
- 流式布局用百分比、fr、minmax 代替固定 px,容器变了内容自动跟着变。
移动优先:先写窄屏再扩展宽屏
响应式最容易搞反的一件事是:先写桌面版,再用媒体查询压成手机版。这种写法会让 CSS 里充满了"本来是三列,窄屏改成一列"的覆盖规则,越写越乱。正确顺序是移动优先——先写手机屏(比如 375px)下的样式,默认就是单列、大按钮、垂直排列;然后用 min-width 媒体查询,在平板和桌面宽度下逐步增强。
/* 第一步:默认样式就是手机版 */
.card-list {
display: grid;
grid-template-columns: 1fr; /* 默认一列 */
gap: 16px;
padding: 16px;
}
/* 第二步:平板宽度以上,变两列 */
@media (min-width: 768px) {
.card-list {
grid-template-columns: repeat(2, 1fr);
gap: 24px;
padding: 24px;
}
}
/* 第三步:桌面宽度以上,变三列 */
@media (min-width: 1200px) {
.card-list {
grid-template-columns: repeat(3, 1fr);
max-width: 1200px;
margin: 0 auto;
}
}
移动优先的好处是:手机用户拿到的就是最精简的 CSS,不需要下载一堆"桌面版专用然后又被覆盖掉"的规则。而且思考顺序也更自然——先想清楚"内容最挤的时候怎么排",再想"宽一点了怎么放开",而不是反过来。
媒体查询与断点怎么选
断点应该选在"布局真的需要变"的宽度,而不是照着某款手机的型号写。常见的做法是先在最窄宽度写好单列布局,然后慢慢拉宽浏览器窗口——当内容看起来挤得不舒服、或者有多余的横向空间被浪费时,那就是加断点的位置。
| 断点 | 典型设备 | 布局变化 |
|---|---|---|
| 默认(<768px) | 手机竖屏 | 单列、垂直导航、汉堡菜单 |
| ≥768px | 平板竖屏 | 两列卡片、横向导航开始露出 |
| ≥1024px | 平板横屏 / 小笔记本 | 三列卡片、侧边栏出现 |
| ≥1280px | 桌面显示器 | 限宽居中、更多列展开 |
几个实用原则:用 min-width 而不是 max-width(配合移动优先);断点不要太多,三四个就够,多了维护不动;不要针对特定设备写断点,比如"iPhone 14 是 390px"——设备每年都在出新的,按内容找断点永远不会过时。
/* 推荐:min-width 移动优先写法 */
@media (min-width: 768px) {
.card-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
.card-grid { grid-template-columns: repeat(3, 1fr); }
}
/* 不推荐:max-width 桌面优先,覆盖规则会越写越多 */
@media (max-width: 767px) {
.sidebar { display: none; }
}
流式布局:用百分比和 fr 代替固定像素
响应式的另一半功夫在布局本身。如果一个容器写死了 width: 960px,那在 375px 的手机上必然出现横向滚动条。流式布局的核心是让尺寸用相对单位表达:宽度用百分比、Grid 用 fr、最大宽度用 max-width 限制上限。
/* 一个自适应的内容容器 */
.container {
width: 100%;
max-width: 1200px; /* 桌面下不会太宽 */
margin: 0 auto; /* 居中 */
padding: 0 16px; /* 两侧留白,手机也不会贴边 */
box-sizing: border-box; /* padding 算在宽度内,不会溢出 */
}
/* 图片自适应:永远不超过容器 */
img {
max-width: 100%;
height: auto;
display: block;
}
/* Grid 流式卡片墙:一行放几个由容器宽度决定 */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 20px;
}
box-sizing: border-box 是全局必备——不然 padding 和 border 会把元素撑出声明的宽度,导致莫名其妙的横向溢出。几乎所有 CSS 重置样式表第一行都是这个。
clamp 与容器查询:少写媒体查询
字号、间距、圆角这些属性,过去要写一堆媒体查询才能在手机和桌面之间平滑过渡。现在一个 clamp() 就能搞定:它接受三个参数——最小值、理想值、最大值——浏览器自动根据视口宽度在三者之间取一个合适的值。
/* 标题:手机上不小于 24px,桌面不大于 48px,中间随视口缩放 */
.hero-title {
font-size: clamp(1.5rem, 1rem + 3vw, 3rem);
line-height: 1.2;
}
/* 段落间距同理 */
.section {
padding-block: clamp(2rem, 1rem + 5vw, 5rem);
}
容器查询(Container Queries)是更新的能力:以前媒体查询看的是整个视口宽度,现在可以看某个容器自己的宽度。一个卡片组件放在侧栏(窄)和放在主区(宽)里,能自动切换内部布局——这在组件化时代特别有用,因为组件自己不再知道自己会被放在哪里。
.card {
container-type: inline-size; /* 声明这是一个查询容器 */
}
@container (min-width: 400px) {
.card-inner { flex-direction: row; } /* 容器宽了再横排 */
}
图片、字体与间距的自适应
布局搞定了,剩下的细节决定页面"看起来专不专业"。三件事特别容易被忽略:
- 图片要按密度提供 srcset。一张 2000px 宽的图在手机上缩到 375px 显示,等于下载了五倍数据。用
srcset加sizes,浏览器自己挑合适的尺寸。 - 字体大小用 rem 而不是 px 堆叠。html 根字号默认 16px,所有元素用 rem 定义字号和间距,用户在浏览器里调大字号时整个页面一起放大,不会破版。
- 触摸目标要够大。手机上按钮和链接的可点击区域至少 44×44px,太小了用户点不准,这是无障碍和体验的双重要求。
<!-- 响应式图片:根据屏幕宽度和像素密度挑最合适的版本 -->
<img src="hero-800.jpg"
srcset="hero-400.jpg 400w, hero-800.jpg 800w, hero-1200.jpg 1200w"
sizes="(max-width: 600px) 100vw, 800px"
alt="示例配图">
横屏和竖屏的切换、深色模式的适配、以及打印样式的处理,都是响应式体系的延伸。现在浏览器对 prefers-color-scheme 和 prefers-reduced-motion 都有很好的支持,用媒体查询照顾这些偏好,页面的专业感会再上一个台阶。
一套可复用的响应式检查清单
写完一个页面,按下面这份清单在 DevTools 里过一遍,能拦下大部分问题:
- 320px:最小宽度下没有横向滚动条,文字不重叠,按钮点得到。
- 375px:主流手机宽度,导航能收起成汉堡菜单,单列布局舒服。
- 768px:平板宽度,两列布局开始生效,图片不变形。
- 1024px:笔记本宽度,三列展开,侧边栏正常出现。
- 1440px 及以上:内容最大宽度被限制,居中显示,不会拉成一长条。
- 横屏切换:手机横过来时布局不崩,可触摸目标仍然够大。
- 字号放大:浏览器调到 200% 缩放,内容仍然可读,不出现重叠。
响应式做到位的标志是:用户根本不需要"适配"这个词。他拿手机打开是手机的样子,拿平板打开是平板的样子,没有人觉得"这个网站专门做了手机版"——因为本来就是同一个页面在自然地变形。这背后是流式布局加少量媒体查询的组合拳,而不是一堆断点硬切出来的。
响应式不是"兼容手机",而是"让布局在任何宽度下都成立"。想通了这一点,写出来的 CSS 就不会满屏都是 hack。