移动端适配与响应式开发
同一个网页要在手机、平板、电脑上都显示得合理,需要解决两件事:
- 移动端适配:让页面在手机上按设计稿比例等比缩放,元素大小随屏幕宽度变化(用
rem、vw/vh、vmin 等单位)。
- 响应式开发:让页面在不同宽度下”换一种布局”,大屏多列、小屏单列(用媒体查询
@media 或栅格系统)。
两者常常配合使用:适配负责”大小”,响应式负责”布局”。
一句话记忆:适配是”同一套尺寸按比例缩放”,响应式是”不同宽度用不同布局”。
一、视口 viewport
移动端浏览器默认会按”桌面宽度”渲染页面,导致页面在手机上被缩小、字看不清。需要用 meta 标签告诉浏览器按设备实际宽度渲染。
1
| <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 参数 |
作用 |
常用值 |
width |
视口宽度 |
device-width(等于设备宽度) |
initial-scale |
初始缩放比例 |
1.0(不缩放) |
maximum-scale |
最大缩放 |
一般可省略 |
user-scalable |
是否允许用户缩放 |
一般不写(保持可缩放更友好) |
⚠️ 易忘点:移动端页面必须写这个 meta 标签,否则媒体查询、vw/vh、rem 都可能在手机上表现不对。BootStrap 的响应式也依赖它。
二、移动端适配单位
1. vw / vh(视口宽高)
1vw = 视口宽度的 1%,100vw = 整个视口宽。
1vh = 视口高度的 1%,100vh = 整个视口高。
1 2 3 4
| .box { width: 100vw; height: 50vh; }
|
2. rem(相对 html 字号)
rem 相对于 **html 元素的 font-size**。
- 浏览器默认
html { font-size: 16px },所以默认 1rem = 16px。
1 2 3
| .box { width: 10rem; }
|
3. vmin / vmax
1vmin = 视口宽高中较小边的 1%。
1vmax = 视口宽高中较大边的 1%。
1 2 3
| .box { width: 50vmin; }
|
竖屏手机上宽度 < 高度,所以 vmin 按宽度算,效果和 vw 一样;横屏时 vmin 会按高度算,更稳。
4. 物理像素与 DPR
- CSS 像素:写代码时的
px。
- 物理像素:屏幕实际的发光点。
- DPR(device pixel ratio,设备像素比) = 物理像素 / CSS 像素。
1
| window.devicePixelRatio;
|
DPR 为 2 表示 1 个 CSS 像素用 2×2 个物理像素显示,所以高清屏下 1px 边框会显得细、图片会发虚。这也是”1px 边框”问题的来源。
三、rem 适配方案
核心思路:让 1rem 随屏幕宽度等比变化,页面上所有用 rem 的尺寸就跟着等比缩放。
1. 原理:动态设置 html 的 font-size
1 2 3 4 5 6 7 8
| function setRemUnit() { var rem = document.documentElement.clientWidth / 10; document.documentElement.style.fontSize = rem + 'px'; } setRemUnit();
window.addEventListener('resize', setRemUnit);
|
这套逻辑就是经典库 flexible.js 的做法(html\06-移动网页开发\js\flexible.js):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| (function flexible(window, document) { var docEl = document.documentElement; var dpr = window.devicePixelRatio || 1;
function setRemUnit() { var rem = docEl.clientWidth / 10; docEl.style.fontSize = rem + 'px'; } setRemUnit(); window.addEventListener('resize', setRemUnit); window.addEventListener('pageshow', function (e) { if (e.persisted) setRemUnit(); }); })(window, document);
|
2. 设计稿 px 转 rem
以 375px 宽的设计稿为例:
1rem = 375 / 10 = 37.5px
- 换算公式:
rem = 设计稿 px ÷ 37.5
| 设计稿 px |
换算 rem |
| 106px |
106 ÷ 37.5 = 2.8267rem |
| 44px |
44 ÷ 37.5 = 1.1733rem |
| 24px |
24 ÷ 37.5 = 0.64rem |
| 15px |
15 ÷ 37.5 = 0.4rem |
1 2 3 4 5
| .box { width: 2.8267rem; height: 2.8267rem; margin-top: .5333rem; }
|
通用公式(设计稿宽度记为 W):1rem = W ÷ 10,rem 值 = 设计稿 px ÷ (W ÷ 10)。
⚠️ 易错点:设计稿不同(375 / 750 / 640),1rem 对应的 px 就不同,换算前先确认设计稿宽度。现在主流是 375px 设计稿。
四、vw 适配方案
vw 本身就相对视口,不需要 JS,是最简单直接的适配方式。
设计稿 px 转 vw
公式:vw = 设计稿 px ÷ 设计稿宽度 × 100
以 375px 设计稿为例:
| 设计稿 px |
换算 vw |
| 106px |
106 ÷ 375 × 100 = 28.2667vw |
| 16px |
16 ÷ 375 × 100 = 4.2667vw |
1 2 3 4
| .box { width: 28.2667vw; height: 28.2667vw; }
|
项目里 index.css 就是 vw 方案(字体、间距、圆角全部用 vw):
1 2 3 4 5 6 7 8
| .home-header { height: 34.6667vw; padding: 8vw 4vw 0; } .search { font-size: 3.4667vw; border-radius: 5.8667vw; }
|
三种方案对比
| 方案 |
是否需要 JS |
相对谁 |
特点 |
rem + flexible.js |
需要 |
html 字号(由 JS 算) |
传统主流,需引脚本 |
vw |
不需要 |
视口宽度 |
纯 CSS,最省事 |
vmin |
不需要 |
视口较小边 |
横竖屏都稳,适合图形 |
index-vmin.css 就是把 index.css 里的 vw 全部替换成 vmin(数值不变),效果一致但更稳健。
五、移动端基础样式重置
移动端有自己的默认样式问题,base.css 一般做这些重置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| body * { box-sizing: border-box; }
a { text-decoration: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); }
input, button, textarea { color: inherit; font: inherit; border: none; }
a:focus, input:focus, button:focus, textarea:focus { outline: none; }
ul, ol { list-style: none; }
|
⚠️ 易忘点:
- 移动端点击
a 会出现灰色/蓝色高亮背景,用 -webkit-tap-highlight-color: transparent 去掉。
input、button 默认不继承字体,要 font: inherit 才能统一字号。
- 去掉
outline 会影响键盘无障碍,实际项目建议换成可见的 focus 样式。
移动端常用布局细节
- 底部固定 tab 栏:
position: fixed; bottom: 0;,并给 body 加 padding-bottom 防止内容被遮挡。
1 2 3 4 5 6 7
| .footer { position: fixed; bottom: 0; left: 0; width: 100%; } body { padding-bottom: 22.6667vw; }
|
- 吸顶 tab 栏:
position: sticky; top: 0;
- 多行省略号:
1 2 3 4 5 6 7
| .info { overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
|
媒体查询是响应式的核心,让不同宽度套用不同样式。
1. 基本语法
1 2 3
| @media 媒体类型 and (媒体特征) { }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @media screen and (min-width: 1200px) { body { background-color: green; } }
@media screen and (min-width: 768px) and (max-width: 1200px) { body { background-color: orange; } }
@media screen and (max-width: 768px) { body { background-color: red; } }
|
- 媒体类型常用
screen(屏幕)、print(打印)。
min-width:大于等于;max-width:小于等于。
- 多个条件用
and 连接;用逗号表示”或”。
2. 两种书写风格
- min-width(移动优先):从小屏写到大屏,逐级增强。
- max-width(桌面优先):从大屏写到小屏,逐级降级。
两者二选一,混用容易乱。本项目多用 max-width。
3. 常见断点
| 断点 |
设备 |
| 576px |
大屏手机 |
| 768px |
平板 |
| 992px |
小笔记本 |
| 1200px |
桌面 |
| 1400px |
大桌面 |
七、响应式布局技巧
1. flex 弹性换行
1 2 3 4 5 6 7
| .box { display: flex; flex-wrap: wrap; min-width: 1215px; max-width: 1780px; margin: 0 auto; }
|
2. 用媒体查询控制每行列数(仿京东)
同一个 .item,在不同宽度下改变它占的百分比,实现”一行 6 个 / 5 个 / 4 个”:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| .box .item { flex: 1; min-width: calc(16.6666667% - 16px); max-width: calc(16.6666667% - 16px); margin: 0 8px 16px; }
@media screen and (min-width: 1316px) and (max-width: 1536px) { .box .item { min-width: calc(20% - 16px); max-width: calc(20% - 16px); } }
@media screen and (max-width: 1316px) { .box .item { min-width: calc(25% - 16px); max-width: calc(25% - 16px); } }
|
3. Grid 自动填充 auto-fit + minmax
让卡片自动计算每行放几个,无需写一堆媒体查询:
1 2 3 4 5
| .course-card { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 40px; }
|
minmax(240px, 1fr) 表示每列至少 240px,放不下就自动换行。
4. 响应式导航栏(桌面↔移动切换)
桌面显示完整导航,小屏隐藏导航、显示汉堡按钮:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| .mobile-nav { display: none; }
@media (max-width: 1024px) { .nav { grid-template-columns: repeat(6, auto); } .nav a:nth-child(4) { display: none; } } @media (max-width: 768px) { .nav { grid-template-columns: repeat(4, auto); } .nav a:nth-child(2) { display: none; } }
@media (max-width: 550px) { .nav { display: none; } .mobile-nav { display: flex; } }
|
5. 响应式图片
1 2 3 4
| img { max-width: 100%; height: auto; }
|
配合 object-fit 让图片不变形地填充:
1 2 3 4 5
| img { width: 200px; height: 150px; object-fit: cover; }
|
八、Bootstrap 栅格系统(快速响应式)
Bootstrap 是流行的响应式框架,核心是 12 列栅格系统。
1. 引入
1 2 3 4 5
| <link href="./bootstrap/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<script src="./bootstrap/bootstrap.min.js"></script>
|
2. 栅格三步:container > row > col
1 2 3 4 5
| <div class="container"> <div class="row"> <div class="col">内容</div> </div> </div>
|
.container:固定宽度的响应式容器(每个断点有对应宽度,自动居中)。
.container-fluid:100% 宽度容器。
- 一行
.row 总共 12 列,用 col-数字 分配宽度。
3. 断点前缀
| 前缀 |
生效宽度 |
示例含义 |
无前缀 col-* |
所有宽度 |
col-6 始终占一半 |
col-sm-* |
≥576px |
小屏及以上 |
col-md-* |
≥768px |
平板及以上 |
col-lg-* |
≥992px |
笔记本及以上 |
col-xl-* |
≥1200px |
桌面及以上 |
col-xxl-* |
≥1400px |
大屏 |
一个元素可同时写多个断点类,在不同宽度下占不同列数:
1 2
| <div class="col-xl-2 col-lg-3 col-md-4 col-sm-6">1</div>
|
对应关系:12 ÷ 列数 = 每行几个。
| 类 |
12 等分里占几份 |
每行个数 |
col-xl-2 |
2 |
6 个 |
col-lg-3 |
3 |
4 个 |
col-md-4 |
4 |
3 个 |
col-sm-6 |
6 |
2 个 |
4. 列对齐
1 2 3 4
| <div class="row align-items-center justify-content-between"> <div class="col-xl-2">1</div> <div class="col-xl-2">2</div> </div>
|
- 垂直:
align-items-start / center / end(还可加 align-self-* 到单个列)。
- 水平:
justify-content-start / center / end / between / around。
5. 常用组件
1 2 3 4 5 6 7 8 9 10 11 12
| <nav class="navbar navbar-expand-lg bg-body-tertiary fixed-top"> <div class="container"> <a class="navbar-brand" href="#">vivo</a> <button class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navMenu"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navMenu"> <ul class="navbar-nav"><li class="nav-item"><a class="nav-link" href="#">首页</a></li></ul> </div> </div> </nav>
|
| 组件 |
类名 |
作用 |
| 按钮 |
btn btn-primary / btn-danger / btn-success |
各种颜色按钮 |
| 卡片 |
card + card-body + card-title + card-img-top |
卡片 |
| 轮播 |
carousel slide + carousel-inner + carousel-item |
轮播图 |
| 手风琴 |
accordion + accordion-item + accordion-button |
折叠面板 |
| 表格 |
table table-striped table-hover |
表格 |
| 表单 |
form-control + input-group |
输入框、输入组 |
| 加载 |
spinner-border |
转圈加载 |
| 工具类 |
text-center、d-none d-md-block、py-5、mt-3 |
排版/显示/间距 |
6. Bootstrap 字体图标
1 2
| <i class="bi bi-wifi"></i> <i class="bi bi-search"></i>
|
通过 CSS 控制大小颜色:
1
| .icon { font-size: 30px; color: red; }
|
记忆点:Bootstrap 图标类名是 bi bi-图标名,本质是字体图标,用 font-size/color 调整。
九、其他响应式/适配技巧
1. CSS 变量统一主题
1 2 3 4 5 6
| :root { --vivo-blue: #1976d2; --vivo-cyan: #00e5ff; --neon-glow: 0 0 10px rgba(0, 229, 255, 0.7); } .card:hover { box-shadow: var(--neon-glow); }
|
2. 图片遮罩 mask-image
让图片边缘渐隐,常用于轮播图底部:
1 2 3 4
| .banner { -webkit-mask-image: linear-gradient(rgb(0,0,0) 80%, rgba(0,0,0,0) 100%); mask-image: linear-gradient(rgb(0,0,0) 80%, rgba(0,0,0,0) 100%); }
|
3. 背景固定 background-attachment: fixed
背景图固定不动,内容滚动时形成视差效果:
1 2 3 4
| .future-tech { background: url(../img/bg1.avif) no-repeat center/cover; background-attachment: fixed; }
|
注意:移动端浏览器对 background-attachment: fixed 支持不稳定,需谨慎。
4. 自定义滚动条
1 2 3 4 5 6
| ::-webkit-scrollbar { width: 10px; } ::-webkit-scrollbar-track { background: #121212; } ::-webkit-scrollbar-thumb { background: linear-gradient(#1976d2, #00e5ff); border-radius: 10px; }
|
5. 防止横向滚动
响应式页面常见”出现横向滚动条”问题,可加:
1
| body { overflow-x: hidden; }
|
(更推荐找到并修复超宽元素,而不是一味隐藏。)
十、速查表
| 需求 |
写法 |
| 移动端视口 |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 视口宽度单位 |
28.2667vw(= 106px @375 设计稿) |
| 相对 html 字号 |
2.8267rem(= 106px @375,1rem=37.5px) |
| 视口较小边 |
50vmin |
| 动态设置 rem |
docEl.style.fontSize = clientWidth / 10 + 'px' |
| 移除点击高亮 |
-webkit-tap-highlight-color: transparent |
| 多行省略号 |
-webkit-line-clamp: 2 + -webkit-box + overflow: hidden |
| 媒体查询 |
@media screen and (max-width: 768px) {} |
| 自动换行网格 |
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)) |
| 响应式图片 |
img { max-width: 100%; } |
| 图片裁剪填充 |
object-fit: cover |
| 栅格容器 |
container / container-fluid |
| 栅格行/列 |
row / col-md-4 |
| 列垂直对齐 |
align-items-center |
| 列水平对齐 |
justify-content-between |
| Bootstrap 图标 |
<i class="bi bi-search"></i> |
| 图片边缘渐隐 |
mask-image: linear-gradient(...) |
| 背景固定视差 |
background-attachment: fixed |
| 自定义滚动条 |
::-webkit-scrollbar-thumb |
十一、复习要点小结
- 移动端必写 viewport meta 标签,否则一切适配失效。
- rem 方案:JS 把屏幕宽分 10 份设
html 字号,1rem = 屏幕宽/10;设计稿 px ÷ 37.5(375 设计稿)。
- vw 方案:无需 JS,设计稿 px ÷ 375 × 100 = vw 值。
- vmin 按宽高较小边算,横竖屏更稳。
- DPR 是物理像素与 CSS 像素之比,影响 1px 边框和图片清晰度。
- 媒体查询
@media (min-width/max-width) 是响应式核心,注意断点和 min/max 的取舍。
- 响应式布局三件套:flex 换行、grid
auto-fit minmax、媒体查询改列数。
- 响应式导航:小屏隐藏整条导航、显示汉堡按钮;Bootstrap 用
navbar-expand-lg + navbar-toggler。
- Bootstrap 栅格:
container > row > col,12 列,col-md-4 等断点前缀控制每行个数。