mcpField
S
safe-area-handling

safe-area-handling

github.com/cap-go设计与 UI731.3k 次安装2 周前更新
代码仓库

关于 safe-area-handling

safe-area-handling 是面向 Capacitor 项目的开发技能,用于诊断和处理 iOS、Android 及窗口化布局中的系统安全区域。它涵盖版本差异、CSS 内边距、状态栏迁移和设备验证步骤。

主要功能

  1. 01检查视口与 Capacitor 配置
  2. 02处理 iOS 和 Android 内边距
  3. 03适配横屏与窗口化布局
  4. 04迁移 Android 状态栏处理
  5. 05提供设备验证与故障排查

适用场景

  1. 01修复内容被刘海或系统栏遮挡
  2. 02排查安全区域值为零或错误
  3. 03处理 Capacitor 升级后的布局问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/cap-go/capgo-skills --skill safe-area-handling

安装后在 Claude Code 中输入/safe-area-handling确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

metadata.json
references/
android-edge-to-edge.md
css-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;Konsta safeAreas: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 <= 7android.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 < 140env(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-* 变量在这两种情况下都会提供正确的值。

工作流

  1. 检查: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 会自行处理内边距)。
  2. 确定设计方案:全出血式(延伸到系统栏下方,并为内容添加内边距)还是受限式(内容位于系统栏之间)。如果原型不明确,请询问用户。
  3. 全出血式(推荐):使用 viewport-fit=cover 和下方的 CSS 回退链,并且只在布局外壳中应用一次,不要逐个组件设置。
  4. 移除旧配置:删除 android.adjustMarginsForEdgeToEdge;移除 Android 上对 StatusBar.setOverlaysWebView / setBackgroundColor 的调用(如有需要,iOS 上保留);优先使用 SystemBars.setStyle 设置图标颜色。
  5. 检查框架:Ionic 已应用 --ion-safe-area-*;Konsta 使用 safeAreas。不要重复添加内边距。
  6. 在设备上验证(见下文)。

仅当相关主题涉及当前问题时,才加载对应参考资料:

基线

<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 添加内边距会导致所有内容偏移两次。

验证

  1. 执行 npx cap sync,并在以下设备上运行:带 Dynamic Island 的 iPhone、iPhone SE 级别的设备(带主屏幕按钮、顶部内边距较小)、iPad(旋转 + Stage Manager/窗口模式)、Android 15/16 设备或模拟器(手势导航和三按钮导航)、横屏。
  2. 检查 Android WebView 版本:adb shell dumpsys package com.google.android.webview | grep versionName(< 140 时依赖注入的变量)。
  3. 在 Safari Web Inspector / chrome://inspect 中运行:
getComputedStyle(document.documentElement).getPropertyValue('--sat');
document.querySelector('meta[name=viewport]').content.includes('viewport-fit=cover');
  1. 确认没有元素与系统栏重叠;标签栏在手势条上方可正常点击;没有重复内边距。
  2. 搜索残留项:
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'(在核心配置声明中;请在你的版本中确认)

资源

评论

支持 Markdown