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 游戏全屏适配的核心就三件事:
- 隐藏原生 UI(导航栏 + 状态栏区域覆盖)
- 处理平台差异(iOS 的侧滑和返回、鸿蒙的 WebView 能力限制)
- 安全区适配(状态栏高度 + 底部 Home 指示条)
技术上没有黑魔法,就是条件编译 + 各平台 API 的正确使用。关键是在动手之前先把三端的 WebView 能力差异搞清楚,否则做到一半发现某个 API 不可用,返工成本很高。
希望这篇文章对正在做 UniApp 鸿蒙适配的同学有帮助。如果你也遇到了类似的坑,欢迎交流。
浙公网安备 33010602011771号