很多人以为 iPhone 的屏幕尺寸就那么几种,适配应该很轻松。真实情况恰好相反: 从 4.7 英寸的 SE 到 6.9 英寸的 Pro Max,再加上刘海、灵动岛、动态岛隐藏态、 安全区、底部 Home Indicator、横竖屏切换与键盘弹起,同一份代码要应对的场景比 Android 更离散。 更麻烦的是,这些差异几乎没有「报错」——它们只会在真机上表现为「差一点」。
下面这七步,是我在 Duo 上按顺序落地的适配流程。每一步都可以独立验收, 并且都可以在 iOS 模拟器里复现。
为什么 2026 年还要专门做 iPhone 适配
三个原因:安全区不再对称、视口高度不再是常量、 WebView 与 Safari 的行为差异。
- 顶部安全区在刘海机型约为 44px,灵动岛机型约为 59px,全屏状态下 App 内 WebView 还会再叠加一层。
- 底部 Home Indicator 固定占掉 34px,横屏时左右两侧也会出现安全区。
- iOS Safari 的
100vh指的是「地址栏收起后」的高度,所以首屏经常被裁掉一截。 - 键盘弹起时,页面并不会自动「变矮」,只是可视视口被覆盖。
一个经验:永远不要用具体的英寸数或机型名来做判断。 只依赖三样东西——视口宽度、安全区变量、视口高度单位。
第 1 步:让页面真正全屏
标配的 viewport 加上 viewport-fit=cover,这是让
env(safe-area-inset-*) 生效的前提。少了这个属性,安全区变量全部返回 0,
页面在刘海机上会老老实实缩进到一块「安全但难看」的区域里。
<!-- 必须带 viewport-fit=cover,否则安全区变量恒为 0 -->
<meta name="viewport"
content="width=device-width, initial-scale=1, viewport-fit=cover" />
如果页面是在 App 的 WebView 里跑,还需要原生侧配合:iOS 上把
WKWebView.scrollView.contentInsetAdjustmentBehavior 设为 .never,
否则系统会额外做一次缩进,导致安全区被计算两次。
第 2 步:安全区,从刘海到灵动岛
安全区变量有四个,可以直接当 CSS 变量用。关键是只给需要贴边的元素加 padding,
不要给 body 整体加,否则背景色会出现难看的留白。
/* 通用写法:给固定定位的头部/底部单独留出安全区 */
.app-header {
padding-top: env(safe-area-inset-top, 0px);
padding-left: env(safe-area-inset-left, 0px);
padding-right: env(safe-area-inset-right, 0px);
}
.tabbar {
padding-bottom: env(safe-area-inset-bottom, 0px);
}
/* 内容区:让背景铺满,但内容躲开安全区 */
.app-main {
padding: max(16px, env(safe-area-inset-top)) 16px
max(24px, env(safe-area-inset-bottom));
}
灵动岛机型(iPhone 14 Pro 及之后)可用同样的方式处理,只是顶部安全区值更大。
如果界面有「全屏沉浸」需求(比如首页大图),做法是让背景元素铺满整屏,
内容层再加 padding-top,两件事分开做就不会互相伤害。
第 3 步:尺寸断点不要按机型写死
下面这张表用来对齐设计稿与逻辑像素,但代码里不要出现这些数字对应的机型名。 实践下来,只用两个断点就足够覆盖全部机型。
| 机型 | 逻辑尺寸 (CSS px) | DPR | 顶部安全区 |
|---|---|---|---|
| iPhone SE (2 / 3 代) | 375 × 667 | 2 | 20px |
| iPhone 13 mini | 375 × 812 | 3 | 50px |
| iPhone 15 | 393 × 852 | 3 | 59px |
| iPhone 16 Pro | 402 × 874 | 3 | 62px |
| iPhone 16 Pro Max | 440 × 956 | 3 | 62px |
推荐的断点策略:min-width: 390px 视为「现代 iPhone 基准」,
min-width: 768px 视为「平板及以上」。宽度用 clamp()
做流体布局,能省掉大量中间断点。
.hero__title {
/* 36px 起步,视口够宽时增长,最多 68px */
font-size: clamp(36px, 7vw, 68px);
}
/* 只在需要时切换布局,而不是给每台机器写一套 */
@media (min-width: 768px) {
.grid { grid-template-columns: repeat(3, 1fr); }
}
第 4 步:动态视口与键盘弹起
解决首屏被裁的问题,用新的视口单位:svh(小视口,地址栏完全展开时的最小高度)、
lvh(大视口)、dvh(动态跟随地址栏变化)。首屏高度建议固定用
svh,避免地址栏收放时页面高度跳动。
/* 首屏:用 svh 保证「无论地址栏什么状态都不会被裁」 */
.hero {
min-height: 100svh;
/* 兼容老版本 iOS 的降级写法 */
min-height: -webkit-fill-available;
}
键盘弹起是另一类问题。iOS 不会缩小布局视口,只会覆盖可视视口,
所以「输入框被键盘挡住」要用 visualViewport 事件来跟:
const vv = window.visualViewport;
if (vv) {
const sync = () => {
// 键盘高度 = 布局视口高度 − 可视视口高度
const keyboard = window.innerHeight - vv.height - vv.offsetTop;
document.documentElement.style.setProperty('--kb', Math.max(0, keyboard) + 'px');
};
vv.addEventListener('resize', sync);
vv.addEventListener('scroll', sync);
sync();
}
拿到 --kb 之后,把底部输入栏的 transform 或底部留白
按这个值处理即可。注意用 transform 而不是改 height,
这样不会触发逐帧重排。
第 5 步:深色模式与 OLED 纯黑
iPhone 的 OLED 屏在纯黑像素上是不发光的,所以深色主题既可以省电,也能让「银河」这类 发光元素更突出。两个细节:
- 给根元素声明
color-scheme: dark,系统控件(滚动条、表单、日期选择器)才会跟着变暗。 - 不要把正文背景设成纯黑
#000,留一点点底色(如#03040b)可以避免 OLED 上黑阶断层与拖影。 - 纯黑背景下的文字对比度要重新检查,正文颜色不低于
#B6BFD8这个亮度更稳。
:root {
color-scheme: dark;
--void: #03040b; /* 深空底色,而非纯黑 */
--ink-2: #b6bfd8; /* 正文,深色底上对比度 ≥ 7:1 */
}
第 6 步:120Hz 与动画性能
iPhone Pro 系列的 ProMotion 屏会把刷新率提到 120Hz。这既是福利也是陷阱:
如果动画只改 transform 与 opacity,画面会顺滑到不可思议;
一旦动画里出现了 width、top、filter 这些属性,
掉帧会比 60Hz 设备上更明显。
- 动画只碰
transform/opacity,必要时用will-change但要记得用完移除。 - 大面积
backdrop-filter: blur()很贵,限制在固定数量的卡片上,不要给列表每一项都加。 - Canvas 动画要按
devicePixelRatio设置画布尺寸,同时给像素比设上限(本站取 2,移动端会动态降到 1.5 以下)。 - 用
requestAnimationFrame的两次时间差做时间步长,而不是假设每帧 16.7ms。
省电策略也别忘了:页面隐藏(
visibilitychange)时要停掉动画循环, 并且完整支持prefers-reduced-motion——系统开了「减弱动态效果」时, 应该退化成一张静态图而不是继续旋转。
第 7 步:横屏、悬停与桌面级 Safari
iPhone 横屏时左右会出现安全区;iPad 与 Mac 上的 Safari 支持鼠标悬停,
但触摸屏没有 :hover。这两件事都可以用媒体查询一次性解决:
/* 只在真有指针悬停能力的设备上启用 hover 效果 */
@media (hover: hover) and (pointer: fine) {
.post:hover { transform: translateY(-3px); }
}
/* 横屏时左右安全区 */
@media (orientation: landscape) {
.shell { padding-inline: env(safe-area-inset-left) env(safe-area-inset-right); }
}
上线前检查清单
| 项目 | 验收方式 |
|---|---|
| 安全区变量生效 | 在 Safari 里检查 env(safe-area-inset-top) 是否非 0 |
| 首屏不被裁切 | 地址栏展开 / 收起两种状态各看一遍 |
| 键盘不遮挡输入框 | 真机点击输入框,观察底部按钮是否被覆盖 |
| 横屏无横向滚动 | 旋转设备后 document.body.scrollWidth 是否等于视口宽 |
| 最小机型可用 | 切到 375 × 667 视口,确认没有元素被挤出 |
| 减弱动效生效 | 设置 → 辅助功能 → 动态效果 → 减弱动态效果 |
| WebView 内行为一致 | Safari → 开发 → 连接真机,远程调试 WKWebView |
最后一句提醒:Safari 的「远程调试」是唯一可靠的验收手段。 桌面浏览器模拟出来的安全区只是近似值,灵动岛、键盘、滚动回弹这些行为, 只有真机能给出答案。
以上就是 Duo 在 iPhone 上的完整适配路径。如果你也在做跨端界面, 欢迎从邮件里把你们的踩坑清单发给我,我会补充进这篇文章。