一篇文章透彻讲懂移动端适配

131 阅读9分钟

📚 前言

在现代前端开发中,用户设备的多样性——从手机、平板到桌面显示器,带来了巨大的适配挑战。如何让网页在不同分辨率、不同像素密度的设备上都能呈现出一致且舒适的视觉体验,已成为每一个开发者必须面对的核心问题。

本文主要分析了 pxremvwcalc()clamp() 这些移动端适配方案的原理和解决思路

首先需要简单引入一个概念:dpi和dpr

DPI 与 DPR 的区别

1. DPI/PPI(Dots Per Inch)

  • 全称:每英寸点数,常用于打印领域。
  • 表示:1 英寸长度上有多少个“点”(dot)。
  • 在屏幕上,更准确的说法是 PPI(Pixels Per Inch) ,即“每英寸像素数”。

💡 举例:

  • 普通笔记本:~100 PPI
  • iPhone 13:~460 PPI(超高密度)

⚠️ 注意
DPI/PPI 越高,屏幕越清晰,但不直接影响 CSS 像素!


2. DPR(Device Pixel Ratio)

  • 全称:设备像素比
  • 定义:1 个 CSS 像素对应多少个物理像素

公式:

  • DPR=CSS 像素宽度(即 window.innerWidth)物理像素宽度

📱 举例 iPhone 13:

  • 物理宽度:1170 px
  • CSS 宽度(window.innerWidth):390 px
  • 所以 DPR = 1170 ÷ 390 = 3

即:1 个 CSS 像素 = 3×3 = 9 个物理像素(用于渲染更清晰的图像)

理解“CSS 像素 vs 物理像素”

设备屏幕宽度(物理像素)CSS 像素(window.innerWidth缩放关系
普通笔记本电脑1920 × 108019201 CSS px = 1 物理 px
iPhone 13 Pro1170 × 2532 物理像素3901 CSS px = 3 物理 px(@3x Retina 屏)
高分辨率显示器(缩放125%)2560 × 14402048浏览器做了缩放处理

🔍 关键点:
即使 iPhone 13 的屏幕更清晰(物理像素更多),但它的 window.innerWidth390px,因为浏览器把它“虚拟化”成了一个更小的逻辑宽度,方便网页适配。


因此我们知道了我们开发过程的像素单位和物理单位的关系

如何判断移动端

不同设备的典型 innerWidth 范围差异巨大,所以我们可以用它做经验性判断

常见设备的 window.innerWidth 范围:

设备类型典型 innerWidth 范围举例
手机(竖屏)320px ~ 480pxiPhone SE: 375px, iPhone 13: 390px
平板(竖屏)481px ~ 768pxiPad: 768px
桌面/笔记本> 768px1366px, 1920px 等

✅ 所以我们通常这样判断:

if (window.innerWidth <= 768) {
  console.log("很可能是手机或小平板");
} else {
  console.log("桌面设备");
}

为什么这个判断是“可靠”的?

因为:

  1. 浏览器自动做了适配
    即使手机物理像素很高(如 1170px),浏览器仍会把 CSS 像素 映射到一个较小的逻辑宽度(如 390px),让网页布局不至于“太小”。
  2. 响应式设计标准
    Bootstrap、Tailwind 等主流框架都基于 innerWidth 设置断点:
    • < 576px: 超小屏(手机)
    • ≥ 768px: 平板
    • ≥ 992px: 桌面
  1. 用户行为一致
    手机用户通常全屏使用,innerWidth 就是屏幕宽度;桌面用户可能缩放窗口,但极少缩到 < 400px。
单位类型相对于什么?
px(CSS)相对单位相对于设备的像素密度和缩放设置
em相对单位相对于父元素字体大小
rem相对单位相对于根字体大小
cm / in绝对单位理论上是物理尺寸,但在屏幕上也受 DPI 影响

💡 所以严格来说:
px 在屏幕上是“相对单位” ,它会根据设备的 DPR(Device Pixel Ratio)系统缩放 动态调整。


举个生活化比喻

想象你在看一张照片:

  • 物理像素 = 照片的真实分辨率(比如 4000×3000 像素)
  • CSS 像素(px) = 你用手机或电脑“查看照片时”的显示尺寸(可能被缩放)

即使照片很清晰,你在手机上看到的“显示宽度”可能只有 390px —— 这就是 window.innerWidth 的逻辑。


接下来进入正题,前端开发中的单位适配,和各自代表的含义

px 是什么?—— CSS 像素(CSS Pixel)

在网页开发中,我们写的 100px 中的 px 并不是物理屏幕上的“物理像素”(Physical Pixel)

而是 CSS 像素(也叫“设备独立像素” Device Independent Pixel, DIP)

物理像素(Physical Pixel)

  • 指屏幕上真实存在的最小发光点(红、绿、蓝子像素组成一个物理像素)。
  • 例如:iPhone 13 的屏幕是 1170 × 2532 物理像素
  • 这是硬件级别的单位,不可变。

CSS 像素(CSS Pixel / Device Independent Pixel)

  • 是浏览器用来布局的逻辑单位,写在 CSS 里的 100px 就是它。
  • 它不是真实的物理点,而是“抽象像素”。
  • 目标:让 100px 在不同设备上看起来大致一样大(视觉尺寸接近)。

📌 定义:

CSS 像素(px) 是一种相对单位,它是浏览器用来布局和渲染网页的逻辑单位,与设备的实际物理像素无关。

  • 虽然 px 是相对单位,但浏览器已经根据设备的分辨率、像素密度、缩放比例,自动将 px 映射为一个“用户可读”的逻辑尺寸
  • 移动设备的 典型逻辑宽度( innerWidth )集中在 320~480px,桌面设备通常 > 768px。
  • 因此,window.innerWidth 提供了一个稳定、可预测的判断依据,即使它是基于“相对单位”。

rem动态适配

px 的“适配”只解决设备密度问题,没解决用户偏好和可访问性问题

  • font-size: 16px → 在用户 B 的浏览器中,仍然显示为 16px
  • font-size: 1rem → 自动跟随浏览器字体设置,变成 16px × 1.5 = 24px

所以:

  • px固定单位,一旦设了就不变;
  • rem相对单位,基于根字体大小(htmlfont-size),默认是 16px,但可被用户修改;

默认情况下,大多数浏览器在不同设备上的根字体大小( 1rem )都是 16px (CSS 像素)

这个值不会因为屏幕分辨率或设备类型自动改变

💡 举例:

  • 在 1920×1080 的笔记本上:1rem = 16px
  • 在 3840×2160 的 4K 显示器上:1rem = 16px(仍然是 16 CSS 像素)

rem 本质:只是一个“相对单位”

  • rem 的定义:相对于根元素 <html> 的字体大小
如果:
html { font-size: 16px; }
那么:
1rem = 16px;
2rem = 32px;
0.5rem = 8px

👉 所以:
html 字体固定为 16px 的情况下, 1rem 就等于 16px ,两者是等价的

为什么rem可以解决布局的问题呢?

rem 能解决不同屏幕的布局问题,核心不是 rem 本身,而是动态调整 html 字体大小的脚本。如果 html font-size 一直是默认的 16px ,那么 16px 1rem 完全没有区别。”

postcss-pxtorem

它会自动把 CSS 中的 px 转成 rem

require('postcss-pxtorem')({
  //根元素字体大小
  rootValue: 16,
  //匹配CSS中的属性,* 代表启用所有属性
  propList: ['*'],
  //转换成rem后保留的小数点位数
  unitPrecision: 5,
  //小于0px的样式不被替换成rem
  minPixelValue: 0,
  //忽略一些文件,不进行转换,比如我想忽略 依赖的UI框架
  // exclude: ['node_modules'],
}),
/* 原始 */
.margin {
  margin-left: 16px;
  font-size: 14px;
}
/* 转换后 */
.margin {
  margin-left: 1rem;
  font-size: 0.875rem;
}

但是只有一个这个postcss是不够的,因为根元素的fontsize不变,即使转换成rem也没有用处

动态根字体脚本
// 动态设置 html 字体大小
function setRootFontSize() {
  const width = Math.min(window.innerWidth, 750); // 最大按 750px 设计稿
  const fontSize = width / 7.5; // 例如:375px → 50px, 750px → 100px
  document.documentElement.style.fontSize = fontSize + 'px';
}

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

💡 这意味着:

  • 手机 375px 宽 → 1rem = 50px
  • 手机 750px 宽 → 1rem = 100px

****🤔 为什么是7.5?

7.5 是一个“设计稿基准换算系数” ,它的来源是:

系数=设计稿宽度(px)/ 100

常见设计稿宽度是 750px,所以: 750/100=7.5

👉 所以: 7.5 不是魔法数字,而是 750px 设计稿的缩放基准。

这样就能做到根据屏幕的尺寸,动态的变化html字体的大小,从而实现元素单位的响应式变化

css适配:vw、vh

/* 让元素占屏幕宽度的 10% */
.box {
  width: 10vw; /* 1vw = 1% 视口宽度 */
}

calc() 动态计算

:root {
  --r: calc(100vw / 750); /* 响应式比例 */
}
.box {
  width: calc(750 * var(--r) * 1px);
  margin: calc(16 * var(--r) * 1px);
} 

calc()“calculate” 的缩写,意思是“计算”。
它允许你在 CSS 中进行数学运算,比如加减乘除,混合使用不同单位。

运算符前后必须有空格

例子 1:动态宽度(减去固定边距)

.container {
  width: calc(100% - 20px);
}

👉 宽度 = 父容器的 100% 宽度 - 20px
(常用于去掉滚动条或边框占用的空间)

例子 2:混合单位(% + px

.sidebar {
  width: calc(30% + 100px);
}

👉 宽度 = 30% 的父容器宽度 + 100px
(适合侧边栏固定宽度 + 弹性部分)

例子 3:响应式字体(结合 vw

.title {
  font-size: calc(16px + 2vw);
}

👉 字体 = 16px + 视口宽度的 2%
(小屏时 16px,大屏时更大)

clamp() 实现流体布局

clamp() 是一个 “限制函数” ,它可以让你设置一个值的最小值、优选值、最大值

  • 如果 优选值 < 最小值 → 使用 最小值
  • 如果 优选值 > 最大值 → 使用 最大值
  • 如果 最小值 ≤ 优选值 ≤ 最大值 → 使用 优选值

它是动态比较的,每时每刻都在根据当前环境重新判断,因此:

中间的“优选值”应该是动态的(如 4vw50%),而两端是静态限制

.title {
  font-size: clamp(16px, 4vw, 24px);
}
  • 小屏:至少 16px(可读)
  • 大屏:最多 24px(不夸张)
  • 中间:按 4vw 平滑变化

移动端适配有不同的方案,但是核心都是响应式,不同实现方法直接各有好坏,要根据不同项目的具体情况,进行设计分析。