摘要:从 16:9 到全面屏,再到挖孔屏、刘海屏,屏幕形态的进化给 H5 页面适配带来了巨大挑战。本文分享了在 HarmonyOS Next 上实现"沉浸式" Web 体验的完整方案,解决了状态栏遮挡内容、底部安全区失效等视觉 Bug。

😵 1. 问题现象

在应用开启"全屏沉浸模式"后,我们兴奋地运行应用,结果被泼了一盆冷水:

  1. 额头撞头:Web 页面的 Header 直接渲染到了状态栏(Status Bar)下面,标题文字和系统时间重叠在一起,根本看不清。
  2. 下巴被切:底部的 TabBar 被系统的全面屏手势条(Home Indicator)遮住了一半,按钮难以点击。

这就是典型的**安全区域(Safe Area)**未适配问题。

📐 2. 理论基础

为了解决异形屏适配问题,W3C 提出了 viewport-fitenv() 环境变量。

  • safe-area-inset-top: 状态栏高度
  • safe-area-inset-bottom: 底部手势条高度
  • safe-area-inset-left/right: 横屏时的左右避让距离

但这需要 Native 容器和 Web 页面的双向配合才能生效。

🔧 3. 实施步骤

3.1 第一步:Native 开启沉浸式 (ArkTS)

默认情况下,ArkWeb 可能不会延伸到状态栏下方。我们需要手动配置窗口布局。

EntryAbility.ets 中:

onWindowStageCreate(windowStage: window.WindowStage) {
    windowStage.getMainWindow().then((win) => {
        // 1. 设置全屏布局(内容延伸到状态栏和导航栏下方)
        win.setWindowLayoutFullScreen(true);
        
        // 2. 获取避让区域高度(用于调试或传递给 JS)
        const type = window.AvoidAreaType.TYPE_SYSTEM;
        const avoidArea = win.getWindowAvoidArea(type);
        console.log('Top Safe Area: ' + avoidArea.topRect.height);
        console.log('Bottom Safe Area: ' + avoidArea.bottomRect.height);
    });
}

3.2 第二步:Web 声明视口 (HTML)

这是最容易被忽略的一步。如果没有这就话,CSS 中的 env() 变量全是 0。

index.html<head> 中修改 viewport

<!-- 关键属性:viewport-fit=cover -->
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">

3.3 第三步:CSS 适配 (Style)

现在可以使用 CSS 变量来撑开间距了。我们推荐使用 CSS Custom Properties (Variables) 做一层封装,方便降级处理。

:root {
    /* 默认值,防止在老旧设备上出错 */
    --sat: env(safe-area-inset-top, 20px);
    --sab: env(safe-area-inset-bottom, 0px);
    --sal: env(safe-area-inset-left, 0px);
    --sar: env(safe-area-inset-right, 0px);
}

/* 顶部导航栏适配 */
.toolbar {
    /* 高度 = 内容高度 + 状态栏高度 */
    height: calc(44px + var(--sat));
    padding-top: var(--sat);
    box-sizing: border-box;
}

/* 底部 TabBar 适配 */
.tabbar {
    height: calc(50px + var(--sab));
    padding-bottom: var(--sab);
    background-color: #fff;
}

/* 侧边抽屉(横屏适配) */
.drawer {
    padding-left: var(--sal);
}

🧪 4. 进阶难题:动态状态栏

在某些场景下,我们可能需要动态切换全屏和非全屏模式(例如图片浏览)。

当 ArkTS 调用 win.setWindowLayoutFullScreen(false) 时,Web 页面内的 env() 变量并不会自动更新!这是 Webkit 的一个已知行为,它通常只在加载时计算一次。

解决方案

我们需要建立一个 Native -> Web 的通知机制。

ArkTS 端

// 监听系统避让区域变化
win.on('avoidAreaChange', (data) => {
    const topHeight = px2vp(data.newAvoidArea.topRect.height);
    const bottomHeight = px2vp(data.newAvoidArea.bottomRect.height);
    
    // 通过 runJavaScript 更新 CSS 变量
    this.controller.runJavaScript(
        `document.documentElement.style.setProperty('--sat', '${topHeight}px');
         document.documentElement.style.setProperty('--sab', '${bottomHeight}px');`
    );
});

Web 端 CSS 调整

:root {
    /* 不再直接使用 env(),而是依赖 JS 注入的变量,env() 仅作为 Fallback */
    --sat: 0px; 
    --sab: 0px;
}

/* 使用 js 注入值的样式 */
body.native-ready {
    /* 此时 CSS 变量已被 JS 更新 */
}

📱 5. 结果展示

  • Mate 60 Pro (三挖孔):顶部 Header 完美避开了摄像头区域,看起来非常自然。
  • MatePad (平板):横屏状态下,左侧菜单没有贴边,留出了手指握持的安全距离。
  • 老旧机型:由于提供了默认值,界面依然整齐,没有崩坏。

💡 6. 总结

刘海屏适配不仅仅是 UI 问题,更是用户体验的红线。

  1. Native 必须开启 LayoutFullScreen,否则 Web 永远拿不到安全区数据。
  2. viewport-fit=cover 是开启 Web 安全区能力的钥匙。
  3. CSS 变量 + calc() 是实现弹性布局的最佳实践。
  4. 对于复杂的动态场景,不要迷信 env()Native 主动注入数据 才是最稳妥的方案。

特别注意:在鸿蒙模拟器中,安全区域数值可能与真机不一致,务必使用真机进行最终验收。

欢迎加入开源鸿蒙PC社区:https://harmonypc.csdn.net/

Logo

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

更多推荐