基于用户反馈及体验测试,当前架构图存在三大核心性能瓶颈:
优化目标:
架构图保存的结构大致是这样的::
const detail = {
'key1': {
name: 'node1',
position: {x:0, y:0},
type: 'product,
},
'key2': {
name: 'node2',
position: {x:0, y:0},
type: 'group,
},
}
...渲染的时候就是一个个节点依次渲染到画布上,所以优化方案就很明确,这里只要把渲染耗时异常的节点类型优化掉就可以了。
此外,对于用户不可见的逻辑,执行不执行对于用户是视野里看不到的,而这段逻辑执行的过程中,大概率用户也操作不到这块逻辑对应的功能,因此没这部分逻辑完全可以放在渲染架构图之后去执行。
console.time对每种类型节点的渲染进行耗时打点,筛选出耗时最高的类型,排查耗时异常点。问题类型 | 优化手段 | 效果 |
|---|---|---|
折线寻路 | 移除初始化寻路,改用静态路径 | 减少80%计算量 |
产品节点 | 合并渲染逻辑 | 减少50%的渲染逻辑 |
组节点 | 延迟渲染缩放控件 | 减少DOM操作 |
根据代码逻辑以及耗时监控得出结论:
节点数量 | 耗时降低 |
|---|---|
300 | 94.3% |
600 | 93.5% |
首先要了解,用户为什么会感觉卡,浏览器一般是60帧,一秒完成60次渲染则丝滑,否则帧率越低,用户感觉越卡顿,如下图所示,可以看到浏览器每一帧都做了哪些内容。

我们要处理的就是Main Thread,这个线程最有可能导致卡顿,主要是因为这里运行了太多任务,比如某个函数需要100ms执行完,那么这100ms浏览器就是卡死的状态。
知道了卡顿原因,优化就好办了,尽可能减少每一帧执行js的耗时就可以了,这里需要重点针对拖拽节点的逻辑进行处理,同样的首先整理拖拽的逻辑链路,核心在mousemove这个函数的处理,卡顿就是每一次mousemove的js运算逻辑过多,超过16.6ms。
数理逻辑后发现每次移动鼠标频繁触发这个逻辑链路,其中,步骤四和五是耗时异常高的,那么针对这两个环节进行优化。
这里我们通过浏览器的perfaomance工具录制移动节点操作,查看函数调用栈找内鬼。

排查下来发现update/mousemove函数耗时最高,接下来对这部分逻辑进行优化。
1)update
检查发现,中间cloneDeep耗时最高,功能需要对节点每个属性深克隆,每个节点几十个属性,几十上百个节点就要深克隆几千次,而该函数耗时又是比较高的,进一步排查发现,很多属性是无需深克隆的,因此代码逻辑修改为如下的伪代码:
try {
// 很多字段无需深克隆
if (NOT_NEED_CLONE_PROPERTY.includes(k)) {
shape[k] = vnode[k];
} else {
shape[k] = structuredClone(vnode[k]);
}
} catch (e) {
// 出现克隆异常全用cloneDeep处理
shape[k] = cloneDeep(vnode[k]);
}这极大减少了深克隆调用次数,需要克隆使用性能更佳的structuredClone,克隆频率降低了70%。
2)mousemove
第一个优化项:__handleRelationsWhenMove耗时是最高的,该函数功能是移动节点的时候检测是否有需要加入的组,很明显该功能不必在每次移动的时候都触发检测,只需要在快要停止移动的时候触发检测就可以了。
// 耗时高的操作待用户快停下的时候再触发
if (Math.abs(diff.x) < 1 && Math.abs(diff.y) < 1) {
this.__handleRelationsWhenMove(checkedNodes);
}第二个优化项:
mousemove触发频率过高,每秒触发一百五十次以上,对于浏览器环境,其实每秒60帧就是丝滑的,因此,做一个限频处理,每秒最多处理60次,伪代码如下所示:
const optMove = throttle((ev) => {
if (isMoving || isMouseUp) return;
isMoving = true;
rafId = requestAnimationFrame(() => {
mouseMove(ev);
isMoving = false;
});
}, 16);第三个优化项:
mousemove的时候频繁触发了折线的findPath自动寻路,这里其实也是不必要的,对折线的拐点(x,y)修改为(x+diff.x,y+diff.y)就可以了,把这个逻辑进行梳理优化,对于同时移动较多折线的场景也得到了较大的提升。
根据代码逻辑以及耗时监控得出结论:
节点数量 | 优化前(ms/frame) | 优化后(ms/frame) | 帧率(FPS) |
|---|---|---|---|
300 | 80 | 23 | 43 |
600 | 140 | 58 | 17 |
(MAC M4 芯片电脑数据,电脑不同数据可能有差异)
帧率能够达到 50 ~ 60 FPS 的动画将会相当流畅,让人倍感舒适;
帧率在 30 ~ 50 FPS 之间的动画,因各人敏感程度不同,舒适度因人而异;
帧率在 30 FPS 以下的动画,让人感觉到明显的卡顿和不适感;
帧率波动很大的动画,亦会使人感觉到卡顿。
可以看到同时移动300节点以内,可以达到40帧,相对来说是比较丝滑的,同时移动600节点有17帧左右,是能用但是感到卡顿的,99%的场景用户同时移动的节点在100个以内,所以基本不会出现卡顿的问题,而少部分场景同时移动操作300节点以上,也能保证基本的移动需求,后续会对这里持续优化。
这里同样使用浏览器的performance排查耗时异常高的函数,排查发现setBaseGroup函数耗时最高,检查生图逻辑,逻辑如下图所示:
这里的逻辑步骤执行次数过多,如果组内节点有一百个,那么要执行一百次加入组的函数,我们可以把这一百次函数用一次批量执行的函数完成操作,即改变执行策略:
这样就极大的降低了函数执行次数,有效提升了是自动生图效率。
自动生图平均耗时降低了50%
如果使用云顾问编辑架构图遇到什么bug,请及时反馈,万分感谢。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。