移动/游戏
在 Capacitor 应用中处理 iPhone 刘海…
当移动端 WebView 应用在刘海屏/挖孔屏设备上出现内容被遮挡、布局错位时,用 CSS env()、JS 读取与原生配置来适配安全区域。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
在 Capacitor 应用中处理 iP特定
解决思路
- 在 index.html 的 viewport meta 中加入 viewport-fit=cover,否则无法访问 safe-area-inset 变量:<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
- 用 CSS 环境变量为各区域加内边距:.header { padding-top: env(safe-area-inset-top); }、.footer { padding-bottom: env(safe-area-inset-bottom); }
- 为 env() 提供回退值:padding-top: env(safe-area-inset-top, 20px);
- 与其他内边距叠加时用 calc():padding-top: calc(env(safe-area-inset-top) + 16px); padding-bottom: calc(env(safe-area-inset-bottom) + 16px);
- 全屏布局:.app 使用 position: fixed; top/left/right/bottom: 0; display: flex; flex-direction: column;
- header 加 padding-top/left/right 的 safe-area-inset;content 用 flex:1、overflow-y:auto、-webkit-overflow-scrolling:touch 并加左右 inset;footer 加 padding-bottom/left/right 的 inset
- Tab Bar:position: fixed; bottom:0; left:0; right:0; display:flex; padding-bottom: env(safe-area-inset-bottom); 子项 flex:1、min-height:49px
- 全出血背景 + 安全内容:.hero 背景铺满,padding-top: calc(env(safe-area-inset-top) + 20px),左右加 inset,内部 .hero-content 限制在安全区内
- JS 读取安全区:创建临时 div 设置 padding 为 env(safe-area-inset-*),插入 body,用 getComputedStyle 读取 padding 值,再写入 CSS 变量 --sat/--sab/--sal/--sar,最后移除临时元素
- 在 orientationchange 事件中延迟约 100ms 重新调用 setSafeAreaProperties 以更新数值
- React 中封装 useSafeArea Hook:用 useState 保存 top/bottom/left/right,useEffect 中创建临时 div 读取 env() 值并 setInsets,监听 resize 与 orientationchange(延迟 100ms),卸载时移除监听
- Vue 中封装 useSafeArea composable:用 ref 保存 insets,onMounted 时 updateInsets 并监听 resize,onUnmounted 时移除监听
- iOS 原生配置:capacitor.config.ts 中设置 ios.contentInset 为 'automatic'(或 'always'、'scrollableAxes'、'never')
- iOS AppDelegate.swift 中在 didFinishLaunchingWithOptions 里将 window.backgroundColor 设为 .clear 以让内容延伸到边缘
- iOS Info.plist 中设置 UIViewControllerBasedStatusBarAppearance 为 true,并按需配置 UISupportedInterfaceOrientations 支持横屏
- Android 在 res/values-v28/styles.xml 中为 AppTheme 设置 <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> 以延伸进挖孔区
- Android MainActivity.kt 中启用 edge-to-edge:API 30+ 调用 window.setDecorFitsSystemWindows(false),低版本设置 decorView.systemUiVisibility 为 SYSTEM_UI_FLAG_LAYOUT_STABLE | LAYOUT_HIDE_NAVIGATION | LAYOUT_FULLSCREEN
- AndroidManifest.xml 的 activity 上设置 android:windowSoftInputMode="adjustResize" 并配置 configChanges 包含 orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode
- 安装并使用 @capacitor/status-bar:npm install @capacitor/status-bar && npx cap sync,然后调用 StatusBar.setStyle({ style: Style.Dark })、setBackgroundColor({ color: '#ffffff' })、hide()/show()、setOverlaysWebView({ overlay: true })
- 内容被刘海遮挡时:加 viewport-fit=cover 并给 body 加 padding-top: env(safe-area-inset-top)
- Tab Bar 被 Home Indicator 遮挡时:给 .tab-bar 加 padding-bottom: env(safe-area-inset-bottom)
- 横屏布局错乱时:给 .content 加 padding-left/right: env(safe-area-inset-left/right)
- 键盘顶起内容时:用 @capacitor/keyboard 监听 keyboardWillShow 将 body paddingBottom 设为 info.keyboardHeight,keyboardWillHide 时恢复为 env(safe-area-inset-bottom)
- 安全区在 WebView 中不生效时:检查 viewport meta 是否严格包含 viewport-fit=cover
- 测试:iOS 模拟器用带刘海的机型(如 iPhone 14 Pro)测试竖屏、横屏与键盘弹出;Android 模拟器创建带摄像头挖孔的机型,测试手势导航与三键导航
- 调试可视化:给容器加 .debug-safe-areas 类,用 ::before 显示顶部红色半透明条(height: env(safe-area-inset-top))、::after 显示底部蓝色半透明条(height: env(safe-area-inset-bottom)),z-index: 9999、pointer-events: none
适用边界 · 注意事项
- 非 Capacitor/WebView 场景(如纯原生 iOS/Android 开发)不适用本技能的 CSS env() 方案
- 目标设备没有刘海、挖孔或手势导航(如老款设备)时无需额外适配
- 未在 viewport meta 中设置 viewport-fit=cover 时,CSS env(safe-area-inset-*) 将始终返回 0,无法生效