关于 safe-area-handling
safe-area-handling 是面向 Capacitor 项目的开发技能,用于诊断和处理 iOS、Android 及窗口化布局中的系统安全区域。它涵盖版本差异、CSS 内边距、状态栏迁移和设备验证步骤。
主要功能
- 01检查视口与 Capacitor 配置
- 02处理 iOS 和 Android 内边距
- 03适配横屏与窗口化布局
- 04迁移 Android 状态栏处理
- 05提供设备验证与故障排查
适用场景
- 01修复内容被刘海或系统栏遮挡
- 02排查安全区域值为零或错误
- 03处理 Capacitor 升级后的布局问题
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ cap-go/ capgo-skills --skill safe-area-handling安装后在 Claude Code 中输入/safe-area-handling确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
metadata.jsonreferences/android-edge-to-edge.mdcss-patterns.md详细介绍
Capacitor 中的安全区域处理
适用场景
在以下情况触发:
- 内容渲染到了状态栏、刘海、Dynamic Island、屏幕开孔、主屏幕指示条或 Android 导航栏/手势栏下方
- Android 上的
env(safe-area-inset-*)为0px,或只有旋转后才正确 - 升级到 Capacitor 8 或将
targetSdkVersion提高到 35/36 后,布局出现问题 StatusBar.setOverlaysWebView/setBackgroundColor在 Android 上停止工作- 横屏/分屏/iPad 窗口化布局的侧边内边距不正确
不适用于:
- 键盘遮挡输入框:
capacitor-keyboard - Ionic
ion-header/ion-toolbar主题设置:ionic-design;KonstasafeAreas:konsta-ui - Tailwind 插件/工具类设置:
tailwind-capacitor - iOS 场景尺寸、
UIScreen.main、多窗口:capacitor-ios-resizability - 启动画面颜色:
capacitor-splash-screen
影响所有决策的事实
| 平台/版本 | 行为 |
|---|---|
| iOS(所有版本) | WebView 为全屏。只有设置 viewport-fit=cover 后,env(safe-area-inset-*) 才有效。ios.contentInset 默认为 never。 |
| Android,Capacitor <= 7 | android.adjustMarginsForEdgeToEdge 配置可以添加原生边距。 |
| Android,Capacitor 8+ | 已移除 adjustMarginsForEdgeToEdge。SystemBars 核心插件(位于 @capacitor/core 中)负责处理内边距。 |
| Android 15(targetSdk 35) | 强制启用边到边显示;仍可通过 windowOptOutEdgeToEdgeEnforcement 选择退出。 |
| Android 16(targetSdk 36,Capacitor 8 默认值;Capacitor 9 为 37) | 忽略选择退出的设置。@capacitor/status-bar 的 overlaysWebView 和 backgroundColor 不再生效。 |
| Android WebView < 140 | env(safe-area-inset-*) 的值错误或为 0(Chromium 缺陷)。SystemBars 会注入 --safe-area-inset-* 变量(insetsHandling: 'css' 为默认值)。 |
Android 上 SystemBars 的行为(基于核心源码):如果页面设置了 viewport-fit=cover,并且 WebView >= 140,内边距会传递给 env(),且 WebView 采用边到边显示。否则,Capacitor 会在原生层为 WebView 添加内边距,使内容位于系统栏之间,并且 env() 返回 0。insetsHandling 为 css 时,--safe-area-inset-* 变量在这两种情况下都会提供正确的值。
工作流
- 检查:
index.html中的 viewport meta、capacitor.config.*(ios.contentInset、android.adjustMarginsForEdgeToEdge、plugins.SystemBars、plugins.StatusBar)、@capacitor/core和@capacitor/status-bar的版本、android/variables.gradle中的targetSdkVersion、设置padding-top的全局 CSS,以及 UI 框架(Ionic / Konsta 会自行处理内边距)。 - 确定设计方案:全出血式(延伸到系统栏下方,并为内容添加内边距)还是受限式(内容位于系统栏之间)。如果原型不明确,请询问用户。
- 全出血式(推荐):使用
viewport-fit=cover和下方的 CSS 回退链,并且只在布局外壳中应用一次,不要逐个组件设置。 - 移除旧配置:删除
android.adjustMarginsForEdgeToEdge;移除 Android 上对StatusBar.setOverlaysWebView/setBackgroundColor的调用(如有需要,iOS 上保留);优先使用SystemBars.setStyle设置图标颜色。 - 检查框架:Ionic 已应用
--ion-safe-area-*;Konsta 使用safeAreas。不要重复添加内边距。 - 在设备上验证(见下文)。
仅当相关主题涉及当前问题时,才加载对应参考资料:
- references/android-edge-to-edge.md - SystemBars 配置、状态栏迁移、导航栏颜色、WebView 版本检查
- references/css-patterns.md - 布局外壳、标签栏、在 JS 中读取内边距、调试覆盖层、可调整大小的窗口
基线
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
:root {
--sat: var(--safe-area-inset-top, env(safe-area-inset-top, 0px));
--sar: var(--safe-area-inset-right, env(safe-area-inset-right, 0px));
--sab: var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px));
--sal: var(--safe-area-inset-left, env(safe-area-inset-left, 0px));
}
.app-header { padding-top: var(--sat); padding-inline: var(--sal) var(--sar); }
.app-tabbar { padding-bottom: var(--sab); padding-inline: var(--sal) var(--sar); }
--safe-area-inset-* 名称由 Capacitor 的 SystemBars 在 Android 上注入;在 iOS 和 Web 上未定义,因此会使用 env() 回退值。
import { SystemBars, SystemBarsStyle } from '@capacitor/core';
await SystemBars.setStyle({ style: SystemBarsStyle.Dark }); // light icons on dark background
SystemBarsStyle.Dark = 深色背景上的浅色内容;Light = 深色内容。iOS 需要设置 UIViewControllerBasedStatusBarAppearance = YES(模板默认值)。
陷阱
- 缺少
viewport-fit=cover是导致 iOS 上env()= 0 以及 Capacitor 在 Android 上为 WebView 添加内边距的首要原因。 - 不要将
ios.contentInset: 'always'与 CSS 安全区内边距一起使用:内边距会重复应用。 - 不要硬编码 20/44/47/59 px 的状态栏高度;高度会因设备、方向和窗口大小而异。
- 内边距会在运行时变化(旋转、iPad 窗口模式、折叠屏、Android 分屏、键盘)。请使用 CSS,或在
resize时重新读取;不要只在启动时缓存一次。 - 在 iOS 上,键盘显示时底部内边距仍会保留;请将键盘高度与
max()一起使用(参见capacitor-keyboard)。 100vh包含系统栏下方的区域;请使用100dvh,或使用带固定外壳的 flex 布局。- Ionic:带有
fullscreen的ion-content和ion-header已经会添加内边距;再给 body 添加内边距会导致所有内容偏移两次。
验证
- 执行
npx cap sync,并在以下设备上运行:带 Dynamic Island 的 iPhone、iPhone SE 级别的设备(带主屏幕按钮、顶部内边距较小)、iPad(旋转 + Stage Manager/窗口模式)、Android 15/16 设备或模拟器(手势导航和三按钮导航)、横屏。 - 检查 Android WebView 版本:
adb shell dumpsys package com.google.android.webview | grep versionName(< 140 时依赖注入的变量)。 - 在 Safari Web Inspector /
chrome://inspect中运行:
getComputedStyle(document.documentElement).getPropertyValue('--sat');
document.querySelector('meta[name=viewport]').content.includes('viewport-fit=cover');
- 确认没有元素与系统栏重叠;标签栏在手势条上方可正常点击;没有重复内边距。
- 搜索残留项:
grep -rn "adjustMarginsForEdgeToEdge\|setOverlaysWebView\|setBackgroundColor\|windowOptOutEdgeToEdgeEnforcement" capacitor.config.* src android/app/src/main/res 2>/dev/null
错误处理
| 症状 / 消息 | 原因 | 修复方法 |
|---|---|---|
iOS 上标题栏位于状态栏下方,env() = 0 | 没有 viewport-fit=cover | 添加到 viewport meta |
Android:内容正常但没有全屏延伸,env() = 0 | 没有 viewport-fit=cover 或 WebView < 140,因此 Capacitor 在原生层添加内边距 | 添加 viewport-fit=cover;使用 --safe-area-inset-* 作为回退 |
| Android:顶部出现双重间隙 | 原生内边距 + 你在 insetsHandling: 'disable' 下设置的 CSS 内边距,或自定义 MainActivity 内边距代码 | 移除自定义的 setDecorFitsSystemWindows / 内边距监听器;保留 SystemBars 默认设置 |
StatusBar.setBackgroundColor / setOverlaysWebView 在 Android 上无效 | targetSdk 35+/36 全屏延伸 | 在 CSS 中绘制系统栏下方的背景;使用 SystemBars.setStyle 设置图标颜色 |
升级到 Capacitor 8 后,配置中仍有 android.adjustMarginsForEdgeToEdge,但边距消失 | 该选项已移除并被忽略 | 删除它;依赖 SystemBars + CSS |
Logcat 中出现 Unknown insetsHandling value '...'. Falling back to 'css'. | SystemBars.insetsHandling 无效 | 使用当前文档列出的值(css、disable;较新的核心版本也接受 native) |
| 状态栏文字不可见(白字白底) | 样式不匹配 | SystemBars.setStyle({ style: SystemBarsStyle.Light }) |
| 首次绘制时布局跳动(Android) | SystemBars 在页面提交后才检测到 viewport-fit | 设置 plugins.SystemBars.initialViewportFitValueHint: 'cover'(在核心配置声明中;请在你的版本中确认) |
资源
- System Bars API:https://capacitorjs.com/docs/apis/system-bars
- Status Bar API(Android 16 说明):https://capacitorjs.com/docs/apis/status-bar
- 升级到 8.0:https://capacitorjs.com/docs/updating/8-0
- Android 全屏延伸:https://developer.android.com/develop/ui/views/layout/edge-to-edge
- WebKit viewport-fit / env():https://webkit.org/blog/7929/designing-websites-for-iphone-x/
评论
支持 Markdown