排查动态柱状图的动画问题
背景
我们基于 ECharts 的竞赛图 封装了一个动态柱状图组件,用于对多周期数据进行动态展示。这是之前的同事封装的,现在业务上线之后,发现当本周期有新数据出现时,效果非常差:
- 新出现的数据中,文字没有随着柱子定位,而是一开始就固定在最终的位置了
- 新出现的柱子的定位是从下往上飞到目标位置,很乱
- 新出现的柱子的高度是从 0 到目标高度,也导致视觉效果很差
业务方预期的效果是类似这样的:
程序流程
通过 console.log(console.trace())查看调用栈。
- 定位到_renderNormal()方法
- 定位到 diff
- 定位到 diff 中的 update
- 发现每次动画都会触发二次 update,第一次是更新数据,第二次排序
- 定位到 updateRealtimeAnimation()
- 定位到 updateProps()
两次渲染流程
首次渲染:setOption,根据原始数据的顺序,计算 layout,创建初始元素
第 2 次渲染:_updateSortWithinSameData,排序,计算实际定位
判断两次渲染的差异变量是isChangeOrder,总之就是 sort 的修改带来了 layout 的变更。
sort 没问题的,不用修改,要改的是sort 之前的逻辑,也就是 setOption 触发的 layout 计算逻辑,这个阶段决定了柱子初始的位置和形状,而我们目前的问题就是柱子初始的位置和形状不对。

完整流程图

解决方案
核心:搞清楚柱子首次渲染的 y 定位和 height 怎么算的。
在 diff 的 update 的 realtimeSortCfg 逻辑中,添加如下代码即可:
1 | |
一些 Q&A
为什么首次的动画是没有上升的?
因为这个首次是真的没有,哪怕首屏没显示的 China,其实也是绘制了的,或者说数据是初始化了的。
初始化的柱子画了个高度=1 的,宽度=value 的,所以当起出现时,看起来高度会变大。
layout 的计算逻辑
1 | |
为什么手动设置的 y 的定位不对?
设置错了,不是 el.y,是 el.shape.y:
1 | |
经验教训
这个问题我从周四下午 3 点开始排查,直到周五晚上 9 点才搞定了,效率极低,主要原因是走了很多弯路,下面总结一下经验教训:
未明确需求
一开始没仔细看出问题的视频,导致我一直在排查文字动画未跟随柱子的问题,实际上业务方预期的效果根本不用改文字,而是应该改柱子动画。
这让我走错了路,浪费了不少时间。
未构建最小、最易排查的数据环境
我是直接在现有 Demo 的大量数据中进行排查的,这会带来一些冗余信息。
工欲善其事,必先利其器,因此第一步应该先精简数据,构建最小、最易排查的程序环境。
后面我改为只有 2 个周期的数据,且营造了大量飞入的数据,这样排查起来就目的性更强了。
输出调试信息时,未增加逻辑断点
当数据较多时,日志会很多,造成大量的干扰信息。
应该添加类似这样的断点:
1 | |
一开始就陷入细节
我一开始就去跟踪代码细节,加上自己不熟悉这部分的代码,导致前面很长时间连问题的关键代码都没定位到。
应该从大流程开始,都不用看代码,先在脑子里面思考下这个动画的流程是怎么样的(数据处理、渲染流程等等),然后再去看代码,别一开始就陷入细节。
没画流程图
当信息很多时(不熟悉的内容,包括流程、程序设计等,往往会给你带来很多信息和概念),我的思维往往很容易乱掉,图是理清楚思维的好办法。周五下午我画出流程图后,很快就理清楚思路,找到了问题的关键点。
没有用 debugger 跟踪代码,导致找漏了修改点
有 3 个地方的修改可能和本次相关:
src/helper/dynamicHistogram
patch-package 修改了 echarts 的源码
src/extension/series/bar/bar.ts 重写了_dataSort
下意识的猜想中,我只找到了第一个,剩下二个根本没想到,后面排查了很久才发现了这 2 个。
如果一开始就 debugger 调试程序,就可以快速定位到后面的 2 个点了。
没有通过注释代码的方式快速定位
后面通过这个方式,很快就定位到了横向生长动画的控制逻辑:
1 | |
确定了和这里修改的属性有关,后续只需要排查这个函数的几个参数(layout)是怎么计算得来的即可。
GPT 解释代码
对于不熟悉的逻辑,让 GPT 去解释,效率极高,而且往往能给我一些惊喜,把潜在的逻辑也解释出来。