🙋🏻♀️ 编者按:本文作者是蚂蚁集团前端工程师芒僧,今年 8 月份开始到 9 月底,「支付宝 - 基金」里面的指数专区进行了一波大改版升级,这次借助 F2 4.0 强大的移动端手势交互能力,我们尝试了一种多维度探索式选基决策工具,本文具体介绍了它是如何实现的。
这次在 「支付宝 - 基金」里的【指数专区改版】需求,我们玩了一种很新的东西 🌝
8 月份开始到 9 月底,「支付宝 - 基金」里面的指数专区进行了一波大改版升级,这次借助 F2 4.0 强大的移动端手势交互能力,我们尝试了一种多维度探索式选基决策工具(如上动图所示)。
简单来说,用户可以在一个散点图上根据「收益」和「波动」这两个维度全览对比整个市场里的指数基金,并选出适合自己的指数基金进行投资,这个功能我们愿称其为「指数图谱」🐶 。
图谱是这个业务场景上的叫法,实际上图谱应该是关系图而非统计图.
功能已发布,页面访问路线如上
先看看有哪些功能点
- 精细打磨的移动端手势交互,平移、缩放、横扫不在话下 :
- 依次为:缩放、平移、横扫
- 底部产品卡和图表的联动交互:
依次为:点击图表上的气泡、滑动底部卡片
- 无惧数据点太多看不到细节,我们有自适应的气泡抽样展示和自动聚焦:
依次为:抽样优化前、抽样优化后
那么,怎么做的呢?
最开始看到这个需求的时候,当时觉得可行性比较低。因为需求里面针对图谱的方案以及细节都特别模糊;不敢承诺各种功能和排期,所以先做了一轮比较完整的系分,增加一些说话的底气 🫣
📱 第一步:同类产品调研
因为设计同学的灵感来自于 大众点评 APP 上面的「美食地图」,所以第一步就是做了一次「同类产品调研」,仔细去看了一下 「美食地图」上究竟有哪些花样,有哪些体验优化的小细节,不看不知道,一看发现细节原来这么多啊 🤕:
图表和卡片的交互联动 | 点抽样展示 | 列表视图和卡片视图可切换 | 交互时卡片自动折叠 | 散点懒加载 | 上滑直接唤起详情页 |
做完这一步之后,大概能够知道自己距离“成品”有多远的距离,方便自己评估工期;另外还可以在系分评审的时候把这些细节提出来,防止临近发布了突然发现某个交互逻辑有个致命的漏洞(别问我怎么知道的,要命的)。这波调研之后,最终我们在实现上致敬了「美食地图」50%
的体验细节优化 (狗头)。
⚙️ 第二步:功能点分析
第二步就是从需求本身的角度做功能点的分析,这样可以方便我们拆分组件,为后续做分层设计打下基础,明白哪些是需要支持可扩展的。这一步大家都熟悉,就不赘述了:
📦 第三步:通用化设计
有了功能点的分析之后,就可以进行通用化的设计了,这就来到了喜闻乐见的沉淀组件的设计环节 🌝
我们希望这个功能不仅仅是纯业务代码,期望下次能够复用大部分核心功能 (理想很丰满),所以在系分的时候是往通用化的方向去设计的,这里主要做了三件事情:分层设计
、概念标准化
、核心流程定义
- 分层设计
拆的逻辑是按最基础的 M(数据层) - C(控制层) - V(视图层)
拆分的。
有了分层设计和功能点分析之后,就可以知道哪些应该放到组件内,哪些接口应该被抽象成通用接口,哪些应该保留扩展性供使用者自己来定义,就可以画个表格了,一一决定哪些模块应该放到组件内:
- 概念标准化
下面来到造词环节,把一些常用的概念都定义成一个个名字,这样方便和后端、设计协同的时候效率更高,同时也方便自己定义清楚各个模型(类)。(这里其实取名越贴切越形象越好,有点考验语言能力了属实是)
- 核心流程定义
这一步是脑补环节,在脑子里跑一遍整体的流程,也是整个需求最核心的流程,比如这里会分成四种流程:初始化流程 、散点图交互流程、底部卡片交互流程、顶部tab交互流程
;
进而可以将四种流程里面的各节点做一些归类,比如都会有图表渲染、数据补全、卡片渲染这些共同的节点,而这些节点就可以实现成具体模型里的具体方法。
🌝 第四步:难点分析
根据上面拆分的各模块,列出哪些点是实现有困难的,耗时长的。这样就可以在评估工期的时候多 Battle 一下,还能砍砍需求,更可以让底层引擎/SDK来突破这些难点(比如找 F2 的核心开发者)
:
📃 最后一步:
按照上述的设计进行代码编写。
难点实现
1. 移动端的图表手势交互体验优化
开发之初,F2 只支持单轴(x或者y)的平移缩放,也不支持全方向交互;在 swipe 上的体验也不太好(阻尼感很强),所以在项目开发过程中, F2 完成了很多体验优化,打磨出很多细致入微的良好体验:
- X轴、Y轴可同时开启平移、缩放
- swiper 体验效果优化
- 移出可视区之后的蒙层遮挡能力(view-clip)
- zIndex 元素层叠渲染
- 平移缩放性能优化
2. 气泡抽样展示优化
因为散点图上的点在初始化的缩放比例下分布非常密集,所以如果每个点上面都绘制一个气泡的话,就会显得密密麻麻的,根本无从下手(如下图1所示)。针对这样的问题,做了「气泡抽样展示」的优化。
实现方式上就是渲染前遍历所有的点,如果在这个点周围某个半径距离之内有其他点,那么就认为这个点是脏点(dirty point),最后筛选出所有“干净”的点进行气泡展示。
如下图图1所示,灰色点(右上角)是干净点,而灰白色的点(偏中间的位置)因为其在圆圈半径范围之内有其他点存在,所以这个点是脏点。
多提一句,这样的过滤方式会使得密集区域的点都不会展示气泡,后续会进行优化。
3. 获取到可视区内的所有点
由于做了气泡抽样展示,所以上图中的底部卡片只会展示用户可视区内散点图上有气泡的点(细心的盆友可以发现,散点图上有两种点,一种是带气泡的交互点,一种是不带气泡的缩略点)。那么就需要一个获取「可视区内所有的点」,实现思路如下:
- 监听 PanEnd(平移结束)、PinchEnd(缩放结束), SwipeEnd(横扫结束)的事件 - 获取到平移/缩放/横扫之后最新的 scales - 根据最新的 scales 里面的 x、y 的 range 过滤一遍图表原数据 - 将脏点从上一步的结果过滤出去 - 底部卡片根据上一步的结果进行渲染展示 - 结束
// 根据当前的缩放比例,拿到「可视区」范围内的数据 function getRecordsByZoomScales(scales, data) { const { x: xScale, y: yScale } = scales; const { field: xField, min: xMin, max: xMax } = xScale; const { field: yField, min: yMin, max: yMax } = yScale; return data.filter((record) => { const isInView = record[xField] >= xMin && record[xField] <= xMax && record[yField] >= yMin && record[yField] <= yMax; return isInView; }); }
// 使用时 export default props => { // 图表原数据 const { data } = props; function handlePanEnd (scales, data) { // 手动高亮下面这一行 getRecordsByZoomScales(scales, data); } return ( <ReactCanvas> <Chart> {/* ... */} <ScrollBar onPanEnd={handlePanEnd}/> </Chart> </ReactCanvas> ) }
4. 数据懒加载
底部卡片的数量是由散点图上点的数量决定的,而每张卡上都有不少的数据量(基金产品信息、指数信息、标签信息),所以不能一次性就把所有点里关联的数据都查询出来(会导致接口返回数据过多)。
这里采取的是懒加载的方式 ,每次只在交互后查询相邻 N+2/N-2 张的卡片数据,并且增加了一份内存缓存来存储已经查询过的卡片数据:
基本的流程图如下:
- 触发散点图交互/滑动底部卡片 - 读取缓存,过滤出没有缓存过的卡片 - 发起数据调用,获取到卡片的数据 - 写入缓存 - 更新卡片数据,返回 - 更新卡片视图,渲染完成
实际线上效果
项目上线之后,我们发现散点图区域的交互率(包含平移,缩放)非常高,可以看出用户对新类型的选基工具抱有新鲜感,也乐于去进行探索;也有部分用户能够通过工具完成决策或者进行产品之间的详细对比(即点击底部卡片上的详情按钮),起到了一个工具类产品的作用 🌝 。
致谢
感谢 AntV 以及 F2 对移动端图表交互能力的支持。