移动端适配从哪开始:viewport、安全区、1px 边框与流式字号

一份按顺序执行的移动端适配清单:viewport meta 三要素、dip 与 DPR 的关系、100vh 在手机上的坑与 svh/dvh 修法、env(safe-area-inset-*) 刘海屏适配、1px 边框的三种实现取舍,以及 clamp() 流式字号让布局在 360–430px 宽度带内自然呼吸。

约 8 分钟读完 · 更新于 2026-10-21 · 文中每条都能在下面的配套工具里动手验证

第一步:viewport meta 三要素

没有这行 meta,手机浏览器会把页面按 980px 宽排版再缩小塞进屏幕——「整体缩小但字小得看不清」就是它的杰作。width=device-width 让布局宽度等于设备 CSS 宽度;initial-scale=1 禁止初始缩放;viewport-fit=cover 让页面延伸进刘海与圆角区域(要用安全区就必开)。这不是可选优化,是移动端适配的第 0 步。

标准 viewport
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">

第二步:搞清 CSS 像素与物理像素

CSS 里的 375px 宽度指的是设备独立像素(dip/iPhone 的 pt),物理像素 = CSS 像素 × devicePixelRatio(DPR)。iPhone 的 DPR 是 3,所以 1px 边框实际由 3 个物理像素渲染——看起来比设计稿的「1px 线」粗。理解这一点,1px 问题和图片清晰度问题都有了同一个解释框架:图片按 2–3 倍尺寸提供、CSS 按 dip 写,两条规则互不冲突。

第三步:100vh 在手机上是不准的

手机浏览器的地址栏收起/展开会改变可视高度,而 100vh 恒等于「最大视口」——地址栏展开时底部会被裁掉一截。新单位修复了它:svh 是最小视口(地址栏展开时),dvh 是动态视口(随地址栏实时变),lvh 才等价旧 100vh。全屏 hero 用 min-height: 100svh 保证内容永远完整可见;需要跟随地址栏变化的吸底工具栏用 100dvh。兼容性兜底写法是先 100vh 再 100dvh。

全屏区块的安全写法
.hero {
  min-height: 100vh;   /* 老浏览器兜底 */
  min-height: 100svh;  /* 地址栏展开时也不裁内容 */
}

第四步:安全区——给刘海和手势条留位置

viewport-fit=cover 开启后,页面会画进刘海与底部手势条区域,固定定位的顶栏底栏必须让开。env(safe-area-inset-top / bottom / left / right) 给出各方向的避让距离(无刘海设备为 0),配合 max() 保证普通设备不受影响:padding-bottom: max(12px, env(safe-area-inset-bottom))。

吸底栏避让手势条
.bottombar {
  position: fixed; left: 0; right: 0; bottom: 0;
  padding-bottom: max(12px, env(safe-area-inset-bottom));
}

第五步:1px 边框的三种实现与取舍

方案写法要点取舍
transform 缩放1px 高度伪元素 scaleY(0.5)(按 DPR)最精确,但多一个伪元素且圆角处麻烦
box-shadow 模拟box-shadow: 0 0.5px 0 #ccc一行解决,但 DPR=1 的桌面会显示 0.5px 直接消失或加粗(浏览器而异)
直接接受 1pxborder: 1px solid最简单;DPR≥2 屏幕上略粗于设计稿,多数产品可接受

第六步:字号用 clamp() 在 360–430px 带内呼吸

主流手机的 CSS 宽度集中在 360–430px,断点跳变在这条窄带里反而生硬。流式字号让数值随视口连续变化:正文 clamp(0.875rem, 0.8336rem + 0.2vw, 1rem) 在 360px 时约 14px、430px 时 16px,无需媒体查询。配合 rem 基准,用户调大浏览器字号时依然全部生效——比整站 rem 缩放方案(html { font-size: 4vw })对无障碍友好得多。工具页的 clamp() 生成器可以直接算出这一行。

上真机前的自查清单

  • 锁屏再解锁、旋转屏幕各试一次:fixed 元素与 100vh/dvh 区块有没有错位
  • 在有刘海的设备(或 DevTools 模拟)检查顶栏是否被刘海压住、底栏是否贴着手势条
  • 把系统字号调到最大档:布局是否仍然成立(rem/clamp 方案应当成立)
  • 输入框聚焦时页面是否被键盘顶起且输入框可见(scroll-into-view 或 visualViewport 监听)
  • Lighthouse 移动端跑一遍:LCP 图片是否按 2x 提供且已压缩

常见问题

为什么手机上 100vh 的区块底部被浏览器工具栏遮住?

100vh 等于最大视口高度(地址栏收起时),地址栏展开时实际可视高度更小。改用 min-height: 100svh(内容永远完整)或 100dvh(随地址栏动态变化),并保留 100vh 作为老浏览器兜底。

1px 边框在手机上看着粗怎么办?

那是 DPR 放大所致。要求高就上 transform scaleY(0.5) 伪元素或 0.5px box-shadow;多数产品直接保留 1px 也可接受,先确认设计是否真的在意。

rem 适配(html font-size 用 vw)和现在推荐的做法冲突吗?

根字号随视口缩放的方案能让整站等比缩放,但会连用户浏览器字号设置一起无视,无障碍受损。更现代的组合是:根字号固定、布局用 rem、需要流式的局部用 clamp() + vw。

📂

松开以自动格式化

支持 .json / .sql / .csv / .html / .css / .xml / .yaml 文件