背景
哈啰出海我的项目目前是基于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发版、审核、下载等繁琐流程,领有了需要随时可发、随时能发的能力。
(本文作者:黄鸿达)
本文系哈啰技术团队出品,未经许可,不得进行商业性转载或者应用。非商业目标转载或应用本文内容,敬请注明“内容转载自哈啰技术团队”。