移动端适配与响应式开发

同一个网页要在手机、平板、电脑上都显示得合理,需要解决两件事:

  • 移动端适配:让页面在手机上按设计稿比例等比缩放,元素大小随屏幕宽度变化(用 remvw/vhvmin 等单位)。
  • 响应式开发:让页面在不同宽度下”换一种布局”,大屏多列、小屏单列(用媒体查询 @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; /* 默认 = 160px */
}

3. vmin / vmax

  • 1vmin = 视口宽高中较小边的 1%。
  • 1vmax = 视口宽高中较大边的 1%。
1
2
3
.box {
width: 50vmin; /* 竖屏手机下约等于 50vw */
}

竖屏手机上宽度 < 高度,所以 vmin 按宽度算,效果和 vw 一样;横屏时 vmin 会按高度算,更稳。

4. 物理像素与 DPR

  • CSS 像素:写代码时的 px
  • 物理像素:屏幕实际的发光点。
  • DPR(device pixel ratio,设备像素比) = 物理像素 / CSS 像素。
1
window.devicePixelRatio; // 普通屏 1,Retina 屏 2 或 3

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() {
// 把屏幕宽度分成 10 等份,1rem = 屏幕宽 / 10
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;

// 设置 1rem = 视口宽度 / 10
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; /* 20px */
}

通用公式(设计稿宽度记为 W):1rem = W ÷ 10rem 值 = 设计稿 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 去掉。
  • inputbutton 默认不继承字体,要 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; /* 最多显示 2 行 */
-webkit-box-orient: vertical;
}

六、媒体查询 @media

媒体查询是响应式的核心,让不同宽度套用不同样式。

1. 基本语法

1
2
3
@media 媒体类型 and (媒体特征) {
/* 满足条件时生效的样式 */
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 屏幕宽度 >= 1200px */
@media screen and (min-width: 1200px) {
body { background-color: green; }
}

/* 768px ~ 1200px 之间 */
@media screen and (min-width: 768px) and (max-width: 1200px) {
body { background-color: orange; }
}

/* 屏幕宽度 <= 768px */
@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); /* 一行 6 个:100/6 */
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); /* 一行 5 个 */
max-width: calc(20% - 16px);
}
}

@media screen and (max-width: 1316px) {
.box .item {
min-width: calc(25% - 16px); /* 一行 4 个 */
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; /* 裁剪填充,类似背景图 cover */
}

八、Bootstrap 栅格系统(快速响应式)

Bootstrap 是流行的响应式框架,核心是 12 列栅格系统

1. 引入

1
2
3
4
5
<link href="./bootstrap/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap 图标 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<!-- 页面底部引入 JS(导航折叠、轮播等组件需要) -->
<script src="./bootstrap/bootstrap.min.js"></script>

2. 栅格三步:container > row > col

1
2
3
4
5
<div class="container">        <!-- 1. 容器,水平居中 -->
<div class="row"> <!-- 2. 行 -->
<div class="col">内容</div> <!-- 3. 列,12 等分 -->
</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
<!-- 一行:≥1200 放6个,≥992 放4个,≥768 放3个,≥576 放2个,更小放1个 -->
<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
<!-- 导航栏(navbar-expand-lg:≥992 展开,否则折叠成汉堡) -->
<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-centerd-none d-md-blockpy-5mt-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

十一、复习要点小结

  1. 移动端必写 viewport meta 标签,否则一切适配失效。
  2. rem 方案:JS 把屏幕宽分 10 份设 html 字号,1rem = 屏幕宽/10;设计稿 px ÷ 37.5(375 设计稿)。
  3. vw 方案:无需 JS,设计稿 px ÷ 375 × 100 = vw 值。
  4. vmin 按宽高较小边算,横竖屏更稳。
  5. DPR 是物理像素与 CSS 像素之比,影响 1px 边框和图片清晰度。
  6. 媒体查询 @media (min-width/max-width) 是响应式核心,注意断点和 min/max 的取舍。
  7. 响应式布局三件套:flex 换行、grid auto-fit minmax、媒体查询改列数。
  8. 响应式导航:小屏隐藏整条导航、显示汉堡按钮;Bootstrap 用 navbar-expand-lg + navbar-toggler
  9. Bootstrap 栅格container > row > col,12 列,col-md-4 等断点前缀控制每行个数。