在 Android 设备上运行 Capacitor 打包的 Vue 3 应用时,经常遇到虚拟导航栏(底部返回键、主页键等)和状态栏遮挡应用内容的情况。这会导致 UI 布局错乱,体验极差。
现象回顾
- 底部 Tab 导航栏被虚拟导航栏遮挡一部分
- 顶部内容被状态栏遮挡
- 页面底部内容贴近虚拟导航栏,没有安全间距
排查思路
起初我们尝试用沉浸式布局,在 MainActivity.java 中设置了透明状态栏和导航栏,让 WebView 内容延伸到系统栏后面:
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
getWindow().setStatusBarColor(Color.TRANSPARENT);
getWindow().setNavigationBarColor(Color.TRANSPARENT);
这使得 WebView 实现了全屏显示,但也带来了新的问题:内容直接跑到了系统栏下面,导致文字不可见或交互区域被覆盖。
踩坑记录
一开始我们假设 CSS 的环境变量能解决问题,于是写了这样的样式:
padding-top: env(safe-area-inset-top, 0);
padding-bottom: env(safe-area-inset-bottom, 0);
结果并不理想。 通过添加调试日志检查控制台输出,发现 Android WebView 对 CSS 的 env() 支持非常有限:
const rootStyles = getComputedStyle(document.documentElement);
console.log('safe-area-inset-top:', rootStyles.getPropertyValue('safe-area-inset-top'));
// 输出为空字符串
实验结论很明确:CSS env() 环境变量在 Android WebView 中返回空值。之前看到的 padding 效果其实是 fallback 值或者硬编码在起作用,并没有真正适配安全区域。
最终方案
既然 CSS 原生变量不生效,我们就改用 JavaScript 在运行时动态计算安全区域高度,并通过 CSS 变量传递给样式层。这样既兼容性好,又能灵活控制。
1. JavaScript 动态计算
在 src/main.ts 中挂载前执行逻辑,估算状态栏和导航栏高度。这里为了减少依赖,没有引入额外的原生插件,而是基于屏幕比例进行估算。
// src/main.ts
const setupSafeArea = async () => {
try {
{ } = ();
(!.()) {
.();
;
}
.();
screenWidth = ..;
screenHeight = ..;
estimatedStatusBarHeight = .(
.(screenHeight * ),
);
estimatedNavBarHeight = .(
.(screenHeight * ),
);
.(, screenWidth, , screenHeight);
.(, estimatedStatusBarHeight, , estimatedNavBarHeight);
root = .;
root..(, );
root..(, );
body = .;
body.. = ;
body.. = ;
.(, estimatedStatusBarHeight, , estimatedNavBarHeight);
} (: ) {
.(, e.);
}
};
app.().$nextTick( () => {
();
});

