UniApp 嵌入 H5 游戏,鸿蒙和 iOS 全屏适配我是这样做的

随着 HarmonyOS NEXT 市场份额逐步扩大,UniApp 开发者迟早要面对一个问题:同一套代码,怎么让 H5 游戏在鸿蒙和 iOS 上都跑满全屏? 这篇文章基于实际项目经验,把踩过的坑和最终方案整理出来,希望对同行有帮助。


背景

我们的 App 基于 UniApp + Vue3 构建,其中游戏模块都是独立 H5 项目,通过 WebView 嵌入 App 运行。游戏要求沉浸式体验——状态栏、导航栏全部隐藏,H5 内容铺满整个屏幕。

听起来简单,但三个平台(Android、iOS、鸿蒙)的 WebView 能力完全不同,一套方案通吃不了。


先搞清楚:三个平台的 WebView 能力差异

这是做适配前必须建立的认知。UniApp 中不同平台的条件编译标识是并列关系:

平台 条件编译标识 WebView 能力
Android APP-PLUS plus.webview 全家桶,能力最全
iOS APP-PLUS plus.webview 大部分可用,部分 API 行为不同
鸿蒙 APP-HARMONY 没有 plus.webview,只能用 <web-view> 组件

注意:APP-PLUS 同时覆盖 Android 和 iOS,鸿蒙是独立的 APP-HARMONY。写了 // #ifdef APP-PLUS 的代码,鸿蒙端不会命中。


第一步:隐藏原生导航栏

全屏的前提是去掉 UniApp 自带的导航栏。在页面路由配置中设置 navigationStyle: 'custom':

{
  path: 'pages/GameWebView/index',
  style: {
    navigationBarTitleText: '',
    navigationStyle: 'custom',  // 关键:隐藏原生导航栏
  },
}

这一步三端通用,没有差异。


iOS 端:用 plus.webview 创建子窗口

核心思路

iOS(和 Android)端通过 plus.webview.create() 创建一个子 WebView,覆盖在当前页面上。设置 top: '0px' + bottom: '0px' 即可实现全屏:

const webViewObj = plus.webview.create(gameUrl, 'my-game-webview', {
  top: '0px',
  bottom: '0px',
  background: '#000000',  // 黑色兜底,防止安全区露白
});

// 挂载到当前页面
const page = getCurrentPages().at(-1);
page.$getAppWebview().append(webViewObj);

就这么简单,H5 游戏就铺满了整个屏幕(状态栏下方,Home 指示条上方)。

坑 1:侧滑返回会误触退出

iOS 默认支持从屏幕左缘右滑返回。在游戏里,这个手势会直接退出游戏,体验极差。

解法:显式禁用侧滑。

const currentWebview = page.$getAppWebview();
currentWebview.setStyle({ popGesture: 'none' });

鸿蒙端:没有 plus.webview,换一种方式

核心思路

鸿蒙端不支持 plus.webview API,这是最大的差异。取而代之的是 uni-app 内置的 <web-view> 组件:

<template>
  <!-- #ifdef APP-HARMONY -->
  <web-view
    v-if="gameUrl"
    :src="gameUrl"
    @message="onMessage"
  />
  <!-- #endif -->
  <!-- #ifndef APP-HARMONY -->
  <view />
  <!-- #endif -->
</template>

<web-view> 组件天然就是全屏的,不需要像 plus.webview 那样手动设置 top/bottom。URL 准备好后赋值给响应式变量,组件自动加载。

坑 1:无法向 H5 注入脚本

plus.webview.evalJS() 在鸿蒙端不可用。这意味着你无法:

  • 动态注入 CSS 变量(比如状态栏高度、主题色)
  • 暂停/恢复 H5 内的音频
  • 调用 H5 暴露的全局方法

解法:把需要传递的信息拼到 URL 参数里,让 H5 自己读取。

// 鸿蒙端:通过 URL 参数传递状态栏高度和主题
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0;
gameUrl.value = `${baseUrl}?token=${token}&statusBarHeight=${statusBarHeight}&theme=green`;

H5 端从 URL 中读取这些参数,自行设置安全区偏移。


安全区:全屏绕不开的最后一道坎

全屏不等于"什么都不挡"。状态栏和底部 Home 指示条仍然占据空间。

状态栏高度

通过 uni.getSystemInfoSync().statusBarHeight 获取,传递给 H5:

const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0;

H5 端使用 CSS 环境变量适配:

.game-header {
  padding-top: env(safe-area-inset-top);
  /* 或使用原生端注入的变量 */
  padding-top: var(--status-bar-height, 0px);
}

底部安全区

在 manifest.json 中设置底部安全区策略:

{
  "safearea": {
    "bottom": { "offset": "none" }
  }
}

设为 "none" 表示不自动添加偏移,由 H5 自行通过 env(safe-area-inset-bottom) 处理。这对游戏来说是最灵活的方式——游戏可以精确控制哪些元素需要避开底部安全区。

viewport-fit

确保 index.html 中的 viewport meta 包含 viewport-fit=cover,这是 CSS env() 生效的前提:

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

一张表看清三端差异

能力 Android iOS 鸿蒙
WebView 创建 plus.webview.create() plus.webview.create() <web-view> 组件
全屏设置 top: '0px' top: '0px' 天然全屏
禁用侧滑 不需要 popGesture: 'none' 无此手势

我的实践建议

1. 条件编译要逐处审查

不要全局替换 APP-PLUS 为 APP-PLUS || APP-HARMONY。全屏相关的代码(如 plus.webview.create 的 top/bottom 参数)是 APP-PLUS 专属逻辑,鸿蒙端走的是完全不同的实现路径,不能盲目合并条件。

2. 给 H5 一个过渡背景

WebView 加载需要时间,全屏模式下没有导航栏遮挡,如果 H5 还没渲染完,用户看到的是整屏空白。建议原生端设置 background 颜色与游戏主色调一致,避免加载时露白。

3. 返回逻辑让 H5 主导

全屏 WebView 下,原生端没有可视的返回按钮。游戏内部的页面跳转和返回,让 H5 自己管理 history 栈,原生端只负责"彻底退出游戏"这个动作。这样三端行为一致,减少平台差异带来的 bug。


写在最后

H5 游戏全屏适配的核心就三件事:

  1. 隐藏原生 UI(导航栏 + 状态栏区域覆盖)
  2. 处理平台差异(iOS 的侧滑和返回、鸿蒙的 WebView 能力限制)
  3. 安全区适配(状态栏高度 + 底部 Home 指示条)

技术上没有黑魔法,就是条件编译 + 各平台 API 的正确使用。关键是在动手之前先把三端的 WebView 能力差异搞清楚,否则做到一半发现某个 API 不可用,返工成本很高。

希望这篇文章对正在做 UniApp 鸿蒙适配的同学有帮助。如果你也遇到了类似的坑,欢迎交流。

posted on 2026-09-20 17:37  码间留白  阅读(12)  评论(0)    收藏  举报

导航