iPhone 17 Pro 顶部安全区已经涨到 62px,你还在 hardcode 44px——今天一个 meta 值加四个 env() 把刘海这件事彻底交给浏览器了

你打开自己维护了三年的移动端页面,顶部导航在 iPhone 12 上看着挺好,到了 iPhone 17 Pro 上又被灵动岛压掉一截——你以为是 padding 写小了,其实是你三年前 hardcode 的那个 44px 到今天已经不对了。顶部的 safe-area-inset-top 已经从 44px 涨到 47、59,再到今天 iPhone 17 Pro 这一档的 62px。

真正的问题不在数字,而在你把「设备几何」当成了常量写进样式表。浏览器早就给你留了四条运行时变量,你只需要让页面先申请进入全面屏,然后把安全区交给浏览器自己算。

先搞清楚这两层东西

动态岛、挖孔、圆角、底部手势条,这些统称「非矩形显示区」。浏览器提供四个环境变量描述它们:

  • env(safe-area-inset-top):顶部,状态栏 / 灵动岛 / 挖孔
  • env(safe-area-inset-bottom):底部,home indicator / 手势条
  • env(safe-area-inset-left) / env(safe-area-inset-right):横屏时刘海移到侧边才会非 0

关键前提只有一个:先加 viewport-fit=cover

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

不加这个值,浏览器默认帮你 letterbox,四个 env() 全部返回 0,你写的 padding 一行都不会生效——这是「env() 根本没反应」最常见的真凶,很多人在这上面耗掉一整个下午。

加上之后,写法就一行:

.app {
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}

/* 想同时保留自己的间距,用 calc() 叠加 */
.toolbar {
  padding-bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
}

第二个参数是 fallback。env(safe-area-inset-bottom, 0px) 让桌面浏览器和还不认识这个变量的旧 WebView 不会把整条声明判为无效——在 calc() 里这两个字就是「到处都能跑」和「直接塌成 0」的区别。

事实:那个数字为什么会一直变

设备几何是硬指标,不是设计偏好。用 1440px 维护的实测数据看,仅 iPhone 竖屏顶部就有好几档:

  • 灵动岛 6.9 英寸档(iPhone 17 Pro Max / 16 Pro Max):顶部 62px
  • 灵动岛 6.3 英寸档(iPhone 17 Pro / 16 Pro):顶部 62px
  • 旧灵动岛档(iPhone 16 Plus / 15 Pro Max):顶部 59px
  • 刘海档(iPhone 11 Pro Max / XR):顶部 48px
  • mini 档(iPhone 13 mini / 12 mini):顶部 44px——就是当年被当成「标准答案」抄遍全网的那个值
  • 带 home 键的 iPhone SE:顶部 20px,底部 0px
  • iPad 各档:顶部 24px,底部 20px

也就是说,从 20 到 62,同一个属性跨了三个数量级档位。你把其中任意一个数字写死进样式表,就等于给所有其它机型埋了一个静默错位——不会报错,不会进控制台,只会在别人手机上悄悄挡住内容。

正解:让浏览器自己算,你只负责申请

2026 年的推荐做法就三件事:

  1. viewport meta 里加 viewport-fit=cover
  2. 所有贴边的交互区域(顶栏、底部 tab、悬浮按钮、抽屉、全屏面板)用 env() 而不是固定 px
  3. 用 calc() 把安全区和你自己的间距叠加,永远保留 fallback
:root {
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
}

.bottom-nav {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  padding: 10px 16px calc(10px + var(--safe-bottom));
}

另外两个容易漏的点:

  • 横屏要四边都 pad。手机一转,刘海跑到侧边,safe-area-inset-left/right 会变成 44~48px,只 pad 上下的布局一转就穿帮。
  • 跨端 WebView 优先读注入变量。2026 年 Capawesome 给出的稳定写法是 var(--safe-area-inset-top, env(safe-area-inset-top, 0px)):Capacitor 8.3.2+ 会同时注入一套 --safe-area-inset-* 自定义属性,覆盖到那批 env() 实现有 bug 的旧 Android WebView;没有注入时自动回落 env(),再回落 0px。

如果你想要一套能覆盖全屏面板、地图、图片查看器的版本:

.screen {
  min-height: 100dvh;
  padding-top: env(safe-area-inset-top, 0px);
  padding-right: env(safe-area-inset-right, 0px);
  padding-bottom: env(safe-area-inset-bottom, 0px);
  padding-left: env(safe-area-inset-left, 0px);
}

注意这里用 100dvh 而不是 100vh——100vh 是浏览器 chrome 收起时的高度,全高元素会比你看到的区域高一截,直到用户滚动才对齐。

今天就动手的三步

  1. 全局搜一遍 safe-area44px34pxpadding-top 里的魔法数字,把「贴边交互区域」的固定值全部换成 env() + calc()
  2. 给 viewport meta 补上 viewport-fit=cover,然后在真机上切横屏验证 left/right 是否生效
  3. 加一层 @supports (padding: env(safe-area-inset-top)) 做最小化降级,避免污染不支持环境的样式,同时不必引入 JS 判断——安全区适配本质是纯 CSS 层的事,加 JS 只会增加首屏阻塞

一句话收尾:刘海和灵动岛的尺寸是设备几何,不是你能控制的常量。让浏览器去算,你只负责申请全面屏和保留 fallback——这件事从 2020 年 env() 进 Baseline 就开始了,只是大多数人的样式表里,还躺着三年前的 44px。

评论区

0 条评论

登录后可评论。

铁锈·Rust工具链 45 阅读