跳到主要内容

响应式设计

问题

什么是响应式设计?媒体查询怎么用?rem、em、vw/vh 等单位有什么区别?Container Queries 解决了什么问题?

答案

响应式设计概述

响应式设计(Responsive Web Design, RWD)的核心思想是让一套代码自适应不同屏幕尺寸。三大基础技术:

  1. 流式布局:使用百分比、Flex、Grid 等弹性布局
  2. 媒体查询:根据设备特征应用不同样式
  3. 弹性媒体:图片/视频随容器自适应
<!-- viewport meta 标签(响应式的前提条件) -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">

媒体查询(Media Queries)

基本语法

/* 屏幕宽度 ≤ 768px 时生效 */
@media screen and (max-width: 768px) {
.sidebar { display: none; }
}

/* 屏幕宽度 ≥ 1024px 时生效 */
@media screen and (min-width: 1024px) {
.container { max-width: 1200px; }
}

现代语法(Range Syntax)

/* 新语法(Chrome 104+、Firefox 63+、Safari 16.4+) */
@media (width <= 768px) { }
@media (768px <= width <= 1024px) { }
@media (width >= 1024px) { }

常用媒体特征

特征说明示例
width / height视口宽高(min-width: 768px)
orientation方向(orientation: portrait)
prefers-color-scheme用户色彩偏好(prefers-color-scheme: dark)
prefers-reduced-motion减少动画(prefers-reduced-motion: reduce)
hover是否支持悬停(hover: hover)
pointer指针精度(pointer: coarse) 触屏
resolution屏幕分辨率(min-resolution: 2dppx)
display-mode显示模式(display-mode: standalone) PWA

逻辑运算

@media screen and (min-width: 768px) and (max-width: 1024px) { }  /* AND */
@media (max-width: 768px), (orientation: portrait) { } /* OR */
@media not print { } /* NOT */

常用断点

推荐断点方案
/* Mobile first(推荐) */
/* 默认样式 → 手机 */
.container { padding: 16px; }

/* 平板 */
@media (min-width: 768px) {
.container { padding: 24px; }
}

/* 桌面 */
@media (min-width: 1024px) {
.container { max-width: 1200px; margin: 0 auto; }
}

/* 大屏 */
@media (min-width: 1440px) {
.container { max-width: 1400px; }
}
Mobile First vs Desktop First
  • Mobile Firstmin-width,从小到大):推荐,移动端优先,渐进增强
  • Desktop Firstmax-width,从大到小):桌面端优先,优雅降级

Mobile First 性能更好——移动设备只需解析默认样式,无需匹配媒体查询。

CSS 单位

相对单位对比

单位相对于说明
em父元素的 font-size可累积,嵌套时会逐层放大/缩小
rem根元素(<html>)的 font-size不受嵌套影响,推荐用于全局
%父元素的对应属性width: 50% = 父宽度的一半
vw / vh视口宽度/高度的 1%100vw = 视口宽度
vmin / vmax视口较小/较大边的 1%常用于保持比例
svw / svh小视口(不含地址栏)移动端地址栏收起时
lvw / lvh大视口(含地址栏)移动端地址栏展开时
dvw / dvh动态视口推荐,自动适应地址栏变化
ch字符 0 的宽度适合限制每行字符数
lh当前行高
cqw / cqh容器查询宽/高的 1%配合 Container Queries
移动端 100vh 问题

在移动端浏览器中,100vh 包含了地址栏区域,导致内容被遮挡。使用 100dvh(动态视口高度)或以下方案解决:

.full-height {
/* 兼容方案 */
height: 100vh;
height: 100dvh; /* 覆盖,支持的浏览器会使用这个 */
}

em vs rem

html { font-size: 16px; }

.parent {
font-size: 20px;
}
.child {
font-size: 1.5em; /* 20px × 1.5 = 30px(相对父元素) */
font-size: 1.5rem; /* 16px × 1.5 = 24px(相对根元素) */
padding: 1em; /* 当在 font-size 以外使用时,em 相对于当前元素的 font-size */
}

rem 适配方案

/* 基础 rem 方案 */
html {
font-size: 16px;
}

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

/* 或使用 clamp() 实现平滑过渡 */
html {
/* 最小 14px,理想 1vw + 10px,最大 18px */
font-size: clamp(14px, 1vw + 10px, 18px);
}

Container Queries(容器查询)

Container Queries 根据父容器的尺寸(而非视口)来应用样式,解决了组件在不同容器中自适应的问题。

Container Queries 基础用法
/* 1. 声明容器 */
.card-wrapper {
container-type: inline-size; /* 声明为容器 */
container-name: card; /* 命名(可选) */
}

/* 2. 基于容器宽度应用样式 */
@container card (min-width: 400px) {
.card {
display: flex;
flex-direction: row;
}
}

@container card (max-width: 399px) {
.card {
display: flex;
flex-direction: column;
}
}
container-type 取值
.wrapper {
container-type: inline-size; /* 在行内轴(通常是宽度)上建立容器 */
container-type: size; /* 宽高都建立容器 */
container-type: normal; /* 不建立容器(默认) */
}
媒体查询 vs 容器查询
  • 媒体查询:基于视口尺寸 → 适合页面级布局
  • 容器查询:基于父容器尺寸 → 适合组件级自适应

同一个卡片组件,在侧边栏中可能是竖版,在主内容区可能是横版——这正是 Container Queries 解决的问题。

响应式图片

<!-- srcset + sizes -->
<img
src="small.jpg"
srcset="small.jpg 480w, medium.jpg 768w, large.jpg 1200w"
sizes="(max-width: 768px) 100vw, 50vw"
alt="响应式图片"
/>

<!-- picture 元素 -->
<picture>
<source media="(min-width: 1024px)" srcset="desktop.webp" type="image/webp" />
<source media="(min-width: 768px)" srcset="tablet.webp" type="image/webp" />
<img src="mobile.jpg" alt="响应式图片" />
</picture>

clamp() 流式排版

clamp(min, preferred, max) 实现无断点的平滑缩放:

h1 {
/* 最小 24px,理想值 5vw,最大 48px */
font-size: clamp(1.5rem, 5vw, 3rem);
}

.container {
/* 最小 320px,理想值 90vw,最大 1200px */
width: clamp(320px, 90vw, 1200px);
margin: 0 auto;
}

.gap {
/* 间距也可以响应式 */
gap: clamp(16px, 3vw, 48px);
}

常见面试问题

Q1: rem 和 em 有什么区别?

答案

特性emrem
相对于父元素(或自身用于非font-size属性时)根元素 <html>
嵌套累积会(层层乘以父级)不会
使用场景组件内部间距(与字号成比例)全局字号、间距

最佳实践:font-sizerem,组件内 padding/marginem

Q2: 移动端 1px 边框问题怎么解决?

答案

高 DPR 设备上 1px CSS 像素可能显示为 2-3 物理像素,看起来过粗。解决方案:

/* 方案1: transform 缩放(推荐) */
.border-1px::after {
content: '';
position: absolute;
left: 0; top: 0;
width: 200%; height: 200%;
border: 1px solid #ccc;
transform: scale(0.5);
transform-origin: 0 0;
pointer-events: none;
}

/* 方案2: 0.5px(仅 iOS Safari 支持) */
.border { border-width: 0.5px; }

/* 方案3: box-shadow */
.border { box-shadow: 0 0 0 0.5px #ccc; }

Q3: 什么是 Mobile First?为什么推荐?

答案

Mobile First 即默认样式面向手机,通过 min-width 媒体查询逐渐添加大屏样式:

/* 默认:手机样式 */
.nav { flex-direction: column; }

/* 平板及以上 */
@media (min-width: 768px) {
.nav { flex-direction: row; }
}

推荐原因:

  1. 性能:移动设备不需要匹配额外的媒体查询
  2. 渐进增强:基础功能保证,大屏逐步增强
  3. 符合用户趋势:移动端流量占比超过 60%

Q4: vw/vh 和百分比有什么区别?

答案

特性vw / vh%
相对于视口(viewport)父元素
width: 50vw视口宽度的一半
width: 50%父元素宽度的一半
受父元素影响

常见问题:100vw 在有滚动条的页面中会包含滚动条宽度,导致横向溢出。解决方案:

.full-width {
width: 100%; /* 不包含滚动条 */
/* 或 */
width: 100vw;
margin-left: calc(-50vw + 50%); /* 居中修正 */
}

Q5: Container Queries 和 Media Queries 的区别?

答案

特性Media QueriesContainer Queries
查询对象视口父容器
适用场景页面布局组件自适应
组件复用同一组件在不同位置表现一致同一组件根据容器不同而不同
兼容性所有浏览器Chrome 105+、Safari 16+、Firefox 110+
语法@media@container

Container Queries 最大价值:让组件真正独立,不依赖页面布局。

Q6: clamp() 函数怎么用?

答案

clamp(min, preferred, max) 返回介于 min 和 max 之间的 preferred 值:

/* 流式字号:最小16px,理想4vw,最大32px */
font-size: clamp(1rem, 4vw, 2rem);

/* 等价于 */
font-size: max(1rem, min(4vw, 2rem));

适用场景:字号、间距、容器宽度的平滑响应式缩放,避免硬性断点。

Q7: 如何处理移动端 100vh 的问题?

答案

移动端浏览器地址栏会动态显示/隐藏,导致 100vh 高度不准确:

/* 方案1: dvh(推荐) */
.hero { height: 100dvh; }

/* 方案2: svh(地址栏展开时的高度) */
.hero { height: 100svh; }

/* 方案3: JS 填充 */
.hero { height: calc(var(--vh, 1vh) * 100); }
JS 方案
function setVH(): void {
const vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);
}

window.addEventListener('resize', setVH);
setVH();

Q8: 什么是 prefers-color-scheme?如何实现暗色模式?

答案

/* 检测系统暗色模式偏好 */
@media (prefers-color-scheme: dark) {
:root {
--bg: #1a1a1a;
--text: #e0e0e0;
}
}

@media (prefers-color-scheme: light) {
:root {
--bg: #ffffff;
--text: #333333;
}
}

完整暗色模式方案通常结合 CSS 变量 + JS 切换,详见 CSS 变量与主题切换

相关链接