给鸿蒙手机做一只 3D 宠物:五个把我卡住的问题

晴伴是个小 App:微信、QQ 的消息和手机电量,会变成屏幕上那只果冻宠物的表情和气泡。逻辑层是 uni-app(Vue 3 + Pinia),画面层是一套自绘的 WebGL1 引擎,跑在页面的 ArkWeb 里。

这只果冻最早不是我的。它来自 softie · 软乎乎(源码,MIT,© 2026 yuanyang749):一个原生 WebGPU + three.js 的网页减压玩具。第一次打开那个页面,按住能陷下去、拎起来能拉伸、松手会回弹,当时就一个念头------这东西不该只在浏览器标签页里,它应该长在手机桌面上。

于是有了晴伴:把"软乎乎"搬进 App,再让它替你看消息。搬的过程比想的重------原版是 WebGPU + three.js,而 ArkWeb 里没有 WebGPU,体积和后处理也压不住,只能自己写一套 WebGL1 渲染。

Android 上一切正常,搬到 HarmonyOS NEXT 的第一天,问题连着来。下面这五个都是真实卡住的地方,现象、定位、根因、改法都在。

技术栈

项 版本 / 选择
应用框架 uni-app(Vue 3 + Pinia),打包 npx uni build -p app-harmony
宿主 WebView HarmonyOS NEXT 的 ArkWeb(DevEco 模拟器 127.0.0.1:5555 验证)
画面 自绘 WebGL1(优先 WebGL2 上下文),GLSL ES 1.00
为什么不用 three.js / WebGPU ArkWeb 没有 WebGPU;three.js 的体积和后处理在卡片里也压不住
其它 Web Audio 现场合成音效,不加载音频文件

问题一:萨摩耶变成一块白方块

鸿蒙上宠物位置是一整块奶油色方块,宠物没出来(后面压着授权弹窗)

另一次:宠物出来了,但背后透出一块白色方块

问题 :史莱姆(程序化画出来的)显示正常,换成萨摩耶(一张透明原画铺在同一个果冻体上)就成了整块发白,边缘细节全没了。第一反应是贴图没加载上,但请求是 200、texImage2D 也没报错,控制台安静得像没事------问题不在"有没有图",在"混出来是什么颜色"。

解决:三处一起改。

第一处,混合方程从预乘写法改成非预乘:

js 复制代码
// 改之前:src 被满强度叠加上去
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)

// 改之后:非预乘输出配 SRC_ALPHA
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)

第二处,上传贴图时显式关掉预乘,避免各家实现对 canvas 源处理不一致:

js 复制代码
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1)
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0)

第三处,让失败出声。原来贴图加载失败是个静默 catch,画面只剩背景色,控制台什么都没有;loadImage 也没有超时,候选链卡在 pending 时一张图都画不出来。改成:失败明确报错并退回静态表情;loadImage 承诺永不挂起(失败 resolve(null),8 秒超时)。

这条要说清楚边界:我是按提交记录和代码注释写的,手上没有鸿蒙设备,没在 ArkWeb 上复现过 。本机用同一张原画做对照实验时,透明像素实测是 (0,0,0,0)、半透明像素只占 2.5% 左右,单靠混合方程凑不出"整块白"------真实触发条件可能还牵到贴图候选链(仓库里同名的 .jpg 版本没有 alpha 通道),这点我没有证据,不写结论。

问题二:ArkWeb 里 rAF 和 document.hidden 都不可信

宠物整个消失,只剩「嘿嘿~」气泡和芯片

问题:画好的宠物不动了。不是卡住那种不动,是画面停在第一帧,嘴型、高光都不再变。根因是 ArkWeb 里三条常规假设全部失效:

  • document.hidden 经常一直是 true,哪怕页面就在前台;
  • 页面在 hidden 状态下,requestAnimationFrame 可能不再回调;
  • 页面 onHide 也不可靠。

解决:鸿蒙分支不再信它们,用定时器保底,并且不因为 hidden 停画:

js 复制代码
function schedule() {
  if (destroyed || raf || timer) return
  // ArkWeb 在 document.hidden===true 时经常不再回调 rAF,改用定时器保底。
  if (runtime.harmony) {
    timer = setTimeout(() => { timer = 0; frame(now()) }, 16)
    return
  }
  raf = requestAnimationFrame(frame)
}

function frame(now) {
  // 鸿蒙:document.hidden / 页面 onHide 都不可靠,不能靠它们停画。
  const pageHidden = !runtime.harmony && document.hidden
  if (!paused && !pageHidden) { /* 推进物理、绘制 */ }
  schedule()
}

代价是前台也不省电,但对一只卡片里的宠物来说,能看见比省电重要。

配套还有两条,缺一个都会变成"卡片是空的":

  • 布局没出来时 getBoundingClientRect() 先返回 0,canvas 会被收成 1×1 再也长不回来 → 启动时同步 measure() + paint() 画一帧,并且前 8 秒每 200ms 轮询一次宿主尺寸;
  • ready 只在真的画出来、宿主宽高 ≥ 8 之后才通知逻辑层。早发 ready 会把 fallback(静态表情)藏掉,用户看到的是一张空的奶油色卡片。

问题三:再套一层 web-view,卡片就空了

问题 :想在页面里再套一个 <web-view> "隔离" WebGL,结果子 Web 不跟着卡片走,铺到了窗口原点(模拟器实测约 [0,117][648,676]),卡片里只剩空底,整页左上角多出一块 Web。

解决 :不套娃。画面层改成在页面自己的 ArkWeb 里用 renderjs 建 canvas:

scss 复制代码
页面(uni-app / Vue)
  └─ renderjs  createPetEngine(host)
        └─ <canvas> 画在宿主容器里

<web-view> 在模板里留着,但必须 v-if="useEmbed && alive",而 useEmbed 默认 false。/static/pet/embed.html 只给 H5 调试用(?pet=embed),鸿蒙路径永远不要打开它。

问题四:看得见,点不动

问题:果冻在屏幕上,点它没反应:不揉、不冒泡,像张图片。

解决:查下来是四个原因叠在一起,逐条对应改:

原因 改法
fallback 压在 canvas 上面,opacity: 0 仍然吃触摸 .fallback.hidden 加 visibility: hidden; pointer-events: none
ArkWeb 按下立刻触发 lostpointercapture 不把这次事件当成松手
有 PointerEvent 构造器但不派发 canvas 和宿主同时听 pointer* 与 touch*,50ms 内去重
命中椭球太严,点到卡片空白处没反应 射线打到身体平面后投影回表面,整张卡都能揉

去重那段:

js 复制代码
// 若 pointer 已处理,50ms 内忽略 touch,避免双触发。
if (now() - lastPointerAt < 50) return

还有一条容易漏:监听要挂在宿主容器(.slime-host)上,不只挂 canvas------fallback 或合成层随时可能把 canvas 挡住。

问题五:悬浮窗一开就闪退

问题:Android 上"显示在其他应用上层"是原生悬浮窗,里面放一个 WebView 画宠物。这个做法搬到鸿蒙,一开就闪退。

解决 :不在 ArkWeb 里硬撑,悬浮窗改成原生绘制 ,WebGL 只留在页面里的 ArkWeb。鸿蒙上的"挂在屏幕上"换了路径:不走系统浮窗,走 supportWindowMode 的 floating / split,从后台任务卡片点"小窗"收成约 360×560,同一个 ArkWeb 里的果冻还在。

这条只做到了规避------换成原生绘制之后不再闪退,但没拿到 ArkWeb 被复用到浮窗里的崩溃栈,所以不敢说根因就是它。


*华为手机中渲染的样子

相关推荐
钉先生贾2 小时前
性能、体积与稳定性:怎样写出可信的适配结论
华为·harmonyos
笑 总不会犯错7 小时前
摆脱多端数据撕裂,鸿蒙分布式数据库同步与冲突合并
harmonyos
用户117910488337 小时前
鸿蒙签名失败不是玄学:5 个证据点把 hapsigntool 一次排通 GitHub:swsgbl/hmharness
开源·ai编程·harmonyos
redtank200010 小时前
纯血鸿蒙小工具三件套实测:弹幕手持、爱记录与仓库管理软件
学习·华为·智能手机·harmonyos
用户86194177207411 天前
uni-app 的蓝牙 API 在鸿蒙上不生效?我写了个兼容层,让老代码零改动跑起来
harmonyos
HarmonyOS_SDK1 天前
【HarmonyOS SDK Skills】挂载 Agent Skill,轻松跑通鸿蒙应用广告变现
harmonyos
闫东阳LordGalaxy1 天前
给小艺装上数学能力:数易 App 接入智能体 FunctionComponent 与 A2A 双通路实战
harmonyos
Dream-Y.ocean1 天前
鸿蒙PC平台逻控 PLC 编辑器适配实战:基于 Electron 壳方案的纯 JavaScript 梯形图编程与仿真引擎开发
javascript·编辑器·harmonyos
OH_TPC1 天前
HarmonyOS APP开发---"好物清单"购物清单App,需要用到这个库
harmonyos