应使用@supports检测safe-area-inset变量而非device-width/height媒体查询,因后者在iOS15+不可靠且无法识别viewport-fit=cover;固定定位元素需用constant()/env()动态偏移适配刘海和手势条,viewport meta必须包含initial-scale=1.0。

直接用 @media screen and (device-width: 375px) and (device-height: 812px) 这类硬编码尺寸做 iPhone 机型区分,已经不可靠,尤其在 iOS 15+ 和 iPhone 14/15 Pro 系列上大概率失效。
为什么 device-width/device-height 媒体查询在新 iPhone 上失灵
iOS 15 起系统会动态报告 device-height:横屏时可能返回 375,竖屏返回 812,甚至某些 WebView 下返回 0;device-width 同样不稳定。iPhone 14 Pro(320×844)和 iPhone 15 Pro(320×906)物理尺寸不同,但都共享相同的刘海+灵动岛逻辑——媒体查询无法识别“安全区域”,只认静态分辨率,结果就是漏机型、错匹配、样式漂移。
- 硬写
(device-width: 390px)可能匹配到非 iPhone 设备(如部分 Android 折叠屏) - 加
(-webkit-device-pixel-ratio: 3)也不能保证唯一性,iPhone 13 mini 和 iPhone 12 都是 3x - 不启用
viewport-fit=cover时,env(safe-area-inset-top)根本不会生效,媒体查询更无从感知
@supports 检测 safe-area-inset 才是真适配起点
要适配刘海和手势条,核心不是“这是哪台 iPhone”,而是“浏览器是否支持安全区域变量”。@supports 直接验证环境变量可用性,比猜设备靠谱得多。
- 必须写成
@supports (padding-top: env(safe-area-inset-top))—— 写@supports (env(safe-area-inset-top))是语法错误,整块 CSS 被丢弃 - iOS 11.2+ 支持
env(),11.0–11.1 只认constant(),所以声明顺序必须是:top: constant(safe-area-inset-top); top: env(safe-area-inset-top); top: 0; - Android 主流 WebView(包括小米、OPPO、vivo)基本不支持该检测,别指望它返回
true;这不是 bug,是规范未落地
固定定位元素被遮挡?媒体查询救不了,只能靠 env() 动态偏移
顶部 position: fixed 导航栏被刘海盖住,不是因为屏幕“不够高”,而是它紧贴视口物理顶部(top: 0),而刘海区域在系统层就不可交互。媒体查询改不了 top 值,只有环境变量能动态注入偏移量。
- 对顶部:用
top: constant(safe-area-inset-top); top: env(safe-area-inset-top); top: 0;,顺序不能反 - 对底部手势条:用
bottom: constant(safe-area-inset-bottom); bottom: env(safe-area-inset-bottom); bottom: 0;,padding-bottom对fixed元素无效 - 别在 React/Vue 的
style={{ top: 'env(safe-area-inset-top)' }}里直接写——JS 不解析 CSS 环境变量,得走cssText或外部 class
viewport meta 必须带 initial-scale=1.0,否则 env() 计算基准错乱
env(safe-area-inset-top) 的值依赖于视口缩放状态。如果 <meta name="viewport" content="width=device-width"> 缺少 initial-scale=1.0,iOS Safari 可能触发双击缩放或字体放大,导致安全区域计算失准,env() 返回 0 或异常值。
- 正确写法:
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> -
viewport-fit=cover是关键开关:没它,env()全部返回 0;有它,才真正启用全屏渲染并暴露安全区域变量 - 这个 meta 必须在
最早位置,且不能被 JS 动态插入——晚了就来不及影响初始布局
真正难的不是写对几行 CSS,而是在 viewport-fit=cover、@supports 语法、constant()/env() 降级顺序、以及 JS 注入时机之间卡准每一个环节;漏掉任意一环,env() 就变成静默失效,你还在调试“为什么 iPhone 15 Pro 没反应”。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











