共计 3617 个字符,预计需要花费 10 分钟才能阅读完成。
背景
哈啰出海我的项目目前是基于 Google 地图提供的服务进行地图相干能力的场景利用。
在 H5 侧 Google 动态地图能力是一项免费服务,对于初期咱们进行海内业务拓展摸索中,这部分费用占据了出海营收的一部分。为了缩小 Google 地图的费用收入,后期咱们也进行一系列的产品侧、研发侧的优化,目前整体 Dynamic maps 单均生产大概降落了 50%,但这远远还是不够。
随着业务的一直迭代上线,Native 地图相较于 Web 地图的人造性能劣势日趋显著,首屏体验也随之差距越来越大。
指标价值
为了改善现状,咱们心愿通过在业务层应用 Webview 承载 H5,展现地图局部则应用 Native 地图,而非采纳 Web 地图。
- 解决 Google 地图在 Web 侧服务的免费问题,达到降本的目标。
- 晋升用户体验,追平与竞品地图性能差距。
- 放弃 H5 的开发灵活性,无需保护两套外围主流程业务代码,不便保护的统一性。
设计方案
基于出海现有的需要,咱们采纳 Native 地图 +H5 页面的设计准则,既心愿可能失常解决 H5 页面的事件,又要满足 Native 地图的相干事件派发。
调研初期,咱们曾构想过通过 Native 地图 + 多个 webview 容器承载页面散落的元素。
这种设计方案尽管解决了布局问题,然而却存在不少有余:
- 多个 Webview 之间内存空间不共享,信息同步问题难度较大
- 一个业务页面存在多个 Webview 组件对系统性能是一个高度节约
- 外围主流程中波及多个地图页,相干革新老本偏高,不确定性高,拓展性差
因而,该计划也满足不了出海场景的需要。
在摸索中,咱们借鉴了业内的思路,并进行了可行性评估。最终从出海场景登程,实际出一套基于 React 的交融计划。
框架设计
交融方案设计
页面架构图层
交融技术架构图
数据通讯流程图
预期成果
- 当咱们点击 Webview 内的 H5 元素时,点击事件派发到 Webview 容器解决
- 当咱们操作地图区域时,操作事件派发到 Native 层地图组件解决
- 通过 JSBridge 实现 Native 地图与 Webview 层的信息通信
外围思路
- 思路为页面模块为 Native 地图层 + Webview 层,Webview 层置于 Native 地图层之上并放弃通明
- 通过 Web 提供热区数据模块散布,Native 辨别热区进行 Native 地图层或 Webview 层逻辑散发
- Native 地图层和 Webview 层之间的数据、事件通信通过 JSBridge 进行互相通信
- Native 地图层次要负责提供通用型地图渲染类,供 Web 调用
- Webview 层负责地图渲染数据渲染层解决,不负责地图 UI 绘制渲染
看到这里,有的人可能就会产生疑难,Native 如何依据热区进行辨别?热区模块如何定义?
热区数据与热区坐标
其实在摸索进去之前,咱们并不知道 Native 还有此等“神力”能够再一个页面里对事件进行无效派发,故当咱们确认了这个能力可能失去无效反对时,咱们束缚好相干 Webview 层热区的定义规定,对热区数据进行对立保护与定义。
咱们以左上角作为坐标原点 [0,0] 进行热区的坐标定义,做好事件散发策略。如果手势音讯的产生产生在热区之内,则音讯派发到 Webview 层,否则派发到 Native 层。至于热区像素坐标格局,则采纳基于 Webview 组件左上角为原点[left, top, width, height]。毕竟前人栽树后人乘凉,没必要整太多花活,解决无效问题最要害。
动静更新
因为不同热区模块存在动静变更的场景,所以咱们还须要思考热区的动静更新。
import Bridge from '@/Bridge'
const getHotData = (data: number[]) => {
//...
return [0,0,100,100]
}
useEffect(() => {if (cardRef.current) {
// 获取以后卡片 dom 元素的相干信息
const rect = cardRef.current.getBoundingClientRect();
// 转化成传输的格局坐标规定
const coordinates = getHotData(rect)
// 通过 Bridge 传递 Native
Bridge.setHotZoneInfo(coordinates)
}
}, [cardRef,]);
以上外围代码能够将指定的元素纳入动静热区的监听当中,但还有一部分业务侧的交互是比较复杂且不好收口的,比方不同组件之间的弹窗、Popup、Confirm 之类的一系列全屏元素,这部分的动静更新策略是值得探讨的。
上面咱们将介绍一下基于这个计划所做的全屏元素动静更新热区的策略。
全屏元素监听动静更新
这里咱们采纳的是 MutationObserver 接口,它提供了监督对 DOM 树所做更改的能力,该性能是 DOM3 Events 标准的一部分。DOM 的任何变动,比方节点的增减、属性的变动、文本内容的变动,这个 API 都能够失去告诉。
特点:
- 异步执行,并不会立马执行,期待所有 DOM 扭转完结后触发
- 变动记录封装成数组解决,而不是一条条解决 DOM 变动
- 可察看 DOM 节点所有变动,也可察看某一类变动
因为其异步执行的人造劣势,少了 DOM 的频繁变动,大大有利于性能。
在出海主流程的场景里,咱们只心愿观测弹窗、Popup 等相似的铺满整个屏幕的元素,所以这里咱们只须要关注宽高与客户端宽高匹配的元素。通过 Dom 元素的 getBoundingClientRect 办法拿到对应元素的相干信息。
const width = document.documentElement.clientWidth || document.body.clientWidth;
const height = document.documentElement.clientHeight || document.body.clientHeight;
const clientRect = Dom.getBoundingClientRect()
const isFull = clientRect.width === width && clientRect.height === height
const config: MutationObserverInit = {
attributes: true, // 察看指标属性扭转
attributeOldValue: true, // 记录扭转前的指标属性 不便比照
childList: true, // 示意察看指标子节点变动 增加 / 删除
subtree: true, // 指标及指标后辈扭转都监听
attributeFilter: ['id', 'class', 'style'], // 设置须要被监听的属性
};
变动执行回调函数
let elementPath = [] // 用于保护以后铺满整个屏幕的元素,用于多蒙层之类的场景判断
const callback = (mutationsList: MutationRecord[], observer: MutationObserver) => {
try {for (const mutation of mutationsList) {const { target, type, attributeName, addedNodes} = mutation;
if (type === 'childList') {
let tag = false;
if (addedNodes.length) {// ...} else {// ...}
} else if (type === 'attributes') {if (attributeName === 'class') {//...} else {// attributes 变动}
}
}
} catch (e) {// 异样场景 兜底走 H5 地图}
}
整个全屏判断就都在回调函数里进行判断,如果以后有铺满屏幕的 H5 元素 则通过 Bridge 告知 Native 整个手势派发由 Webview 接管,否则按原有的热区模块进行逻辑散发。
每次的 Callback 触发中,咱们次要仍旧三个外围思路进行元素的查找定位:
- 依据变动节点,向上查找,直到 BODY 完结
- 依据变动节点,向下查找,把节点父容器进行元素遍历
- 依据全屏元素门路,定向查找,把原有记录全屏的节点进行从新校验
测试工具
对于热区
咱们基于 vConsole 编写了一个繁难的插件进行相干 Bridge 的调用以及热区的测试。通过 Canvas 将热区绘制在屏幕中进行相干数据的出现,疾速诊断和排查热区异常情况。
对于地图 Bridge
目前咱们次要在进行相干地图通用能力接口的编写,通过对出入参的数据收集以及整个交互过程配合控制台的输入进行边界问题排查,后续将通过单测的形式对其进行自动化测试。
这一实际,无效的将 Webview 和 Native 组件交融起来,通过该交融技术为出海业务无效晋升开发迭代效率,同时保障了用户地图的体验。除了首次发版依赖 App 发版、审核、下载等繁琐流程,领有了需要随时可发、随时能发的能力。
(本文作者:黄鸿达)
本文系哈啰技术团队出品,未经许可,不得进行商业性转载或者应用。非商业目标转载或应用本文内容,敬请注明“内容转载自哈啰技术团队”。