1. 引言

在移动端 H5 应用开发中,安全区域(Safe Area)适配是绕不开的难题。随着全面屏、刘海屏、挖孔屏以及灵动岛的普及,页面底部和顶部常常被系统手势条、状态栏或摄像头区域遮挡,导致按钮不可点、文字被截断。本文围绕 Android、iOS 和鸿蒙(HarmonyOS)三大平台,系统讲解安全区域适配的原理与最佳实践。

2. 什么是安全区域

安全区域是指系统为应用内容预留的、不会被状态栏、导航栏、手势条、圆角或刘海遮挡的可视区域。不同平台对安全区域的定义和获取方式各不相同,但目标一致:让关键内容始终完整可见、可交互。

常见需要适配的场景包括:

  • 顶部状态栏与刘海区域遮挡标题栏。
  • 底部手势条(Home Indicator)遮挡操作按钮。
  • 横屏时左右两侧的圆角与刘海区域。
  • 键盘弹出时底部输入框被遮挡。

3. iOS 安全区域适配

iOS 从 iPhone X 开始引入刘海屏,系统通过 safe-area-inset-* 环境变量向 WebView 暴露安全区域信息,适配最为规范。

3.1 viewport 设置

在 HTML 的 viewport meta 标签中,必须设置 viewport-fit=cover,否则页面默认不会延伸到安全区域之外,也就无法读取安全区数值。

<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />

3.2 CSS env() 函数

设置 viewport-fit=cover 后,即可通过 CSS 的 env() 函数读取四个方向的安全区 inset 值。

.safe-top {
    padding-top: env(safe-area-inset-top);
}
.safe-bottom {
    padding-bottom: env(safe-area-inset-bottom);
}
.safe-left {
    padding-left: env(safe-area-inset-left);
}
.safe-right {
    padding-right: env(safe-area-inset-right);
}

对于不支持 env() 的旧版本浏览器,建议同时提供 constant() 作为降级方案。

.safe-bottom {
    padding-bottom: constant(safe-area-inset-bottom);
    padding-bottom: env(safe-area-inset-bottom);
}

3.3 底部固定按钮适配

底部固定操作栏是最常见的适配场景,需要同时考虑手势条高度和底部安全区。

.bottom-bar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    padding-bottom: env(safe-area-inset-bottom);
    background-color: #ffffff;
}

4. Android 安全区域适配

Android 生态碎片化严重,不同厂商对刘海屏、挖孔屏的处理方式不同,WebView 对安全区域的暴露也不如 iOS 统一。Android 端通常需要结合原生代码与 CSS 变量共同处理。

4.1 原生侧获取安全区

在 Android 原生层,可以通过 WindowInsets 获取系统栏和安全区域信息,再通过 JavaScript 桥接注入到 H5 页面。

ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
    val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
    val displayCutout = insets.getInsets(WindowInsetsCompat.Type.displayCutout())
    val top = maxOf(systemBars.top, displayCutout.top)
    val bottom = maxOf(systemBars.bottom, displayCutout.bottom)
    // 通过 WebView 注入 JS
    webView.evaluateJavascript(
        "window.safeArea = { top: $top, bottom: $bottom, left: 0, right: 0 };",
        null
    )
    insets
}

4.2 前端读取注入值

原生注入后,前端通过读取 window.safeArea 动态设置 CSS 变量,实现安全区适配。

function applySafeArea() {
    const safe = window.safeArea || { top: 0, bottom: 0, left: 0, right: 0 };
    const root = document.documentElement;
    root.style.setProperty('--safe-top', safe.top + 'px');
    root.style.setProperty('--safe-bottom', safe.bottom + 'px');
    root.style.setProperty('--safe-left', safe.left + 'px');
    root.style.setProperty('--safe-right', safe.right + 'px');
}
window.addEventListener('DOMContentLoaded', applySafeArea);
.bottom-bar {
    padding-bottom: var(--safe-bottom, 0px);
}

4.3 全面屏与手势导航

Android 10 及以上系统默认启用全面屏手势导航,底部手势条区域同样需要预留空间。建议在原生层将 WindowInsetsCompat.Type.systemBars() 与 navigationBars() 一并纳入计算。

5. 鸿蒙(HarmonyOS)安全区域适配

鸿蒙系统在 ArkWeb 组件中提供了安全区域相关能力,适配思路与 Android 类似,但 API 名称和注入方式有所不同。

5.1 ArkWeb 安全区获取

在鸿蒙原生侧,可以通过 onAreaChange 或系统提供的安全区域接口获取避让区域信息,再注入到 H5 页面。

// ArkTS 示例
this.webController.onAreaChange((oldValue, newValue) => {
    const top = newValue.topRect.height;
    const bottom = newValue.bottomRect.height;
    this.webController.runJavaScript(
        `window.safeArea = { top: ${top}, bottom: ${bottom}, left: 0, right: 0 };`
    );
});

5.2 前端统一适配方案

鸿蒙端同样建议通过注入 window.safeArea 的方式,与 Android 共用同一套前端适配逻辑,降低维护成本。

function getSafeArea() {
    return window.safeArea || { top: 0, bottom: 0, left: 0, right: 0 };
}

5.3 注意事项

  • 鸿蒙 WebView 对 env(safe-area-inset-*) 的支持尚不统一,建议优先使用原生注入方案。
  • 鸿蒙的避让区域可能包含顶部状态栏、底部导航条以及折叠屏的铰链区域,需要按实际设备验证。

6. 三端统一适配方案

为了降低多端维护成本,推荐采用「原生注入 + CSS 变量 + 兜底值」的统一方案。

6.1 统一前端逻辑

const DEFAULT_SAFE = { top: 0, bottom: 0, left: 0, right: 0 };

function initSafeArea() {
    const safe = window.safeArea || DEFAULT_SAFE;
    const root = document.documentElement;
    root.style.setProperty('--safe-top', safe.top + 'px');
    root.style.setProperty('--safe-bottom', safe.bottom + 'px');
    root.style.setProperty('--safe-left', safe.left + 'px');
    root.style.setProperty('--safe-right', safe.right + 'px');
}

// iOS 优先使用 env()
if (window.safeArea === undefined) {
    const style = document.documentElement.style;
    style.setProperty('--safe-top', 'env(safe-area-inset-top, 0px)');
    style.setProperty('--safe-bottom', 'env(safe-area-inset-bottom, 0px)');
    style.setProperty('--safe-left', 'env(safe-area-inset-left, 0px)');
    style.setProperty('--safe-right', 'env(safe-area-inset-right, 0px)');
}

initSafeArea();

6.2 统一 CSS 使用方式

.page-header {
    padding-top: var(--safe-top, 0px);
}
.page-footer {
    padding-bottom: var(--safe-bottom, 0px);
}

7. 常见问题与避坑指南

  • iOS 未设置 viewport-fit=cover:env() 读取不到安全区数值,页面内容无法延伸到刘海区域。
  • Android 机型差异:不同厂商对刘海高度的上报不一致,建议以真机实测为准。
  • 横屏适配遗漏:横屏时左右两侧的 safe-area-inset-left/right 同样需要处理。
  • 键盘弹出遮挡:底部输入框需要监听键盘高度,动态调整布局,不能只依赖安全区。
  • 鸿蒙版本差异:不同 HarmonyOS 版本对安全区 API 的支持有差异,需要做好版本兼容。

8. 总结

安全区域适配是移动端 H5 开发的基础能力,三端适配的核心思路是一致的:先获取系统安全区数值,再通过 CSS 变量或 env() 应用到页面布局。iOS 依赖系统内置的 env() 机制,Android 和鸿蒙则更适合通过原生注入统一处理。建议在项目中封装一套统一的安全区工具函数,配合真机测试覆盖主流机型,才能保证页面在各类全面屏设备上都有良好的体验。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐