又一个大屏性能优化
起因
又一个大屏项目出现了性能问题:
大致描述下此大屏的特性:
- 顶部不规则扇形被切割为5个「阶段」
- 每一阶段需要状态动态设置颜色以及流光效果
- 「阶段」中存在「流程」,最多能容纳「5」个流程,所以多出来的得进行分页轮播
- 底部有不定数量的「系统」,每个「系统」包含系统名称和切换「系统」所耗时间
- 由于系统名称可能文字过长,所以一旦超出宽度得无缝滚动从左往右播放
- 切换「系统」所耗时间每隔 1s 累加
- 底部 3d 连线有虚线流动的动画效果,且根据各自「系统」是否正在切换为依据设置每条线的动画是否启用
- 大屏左右两侧是 ECharts 折线图表
好,至此大屏的功能大致讲清楚了。现在的问题是功能实现了,但上到客户生产环境上动画贼卡。现在客户诉求是在不允许更换电脑(4G内存,英伟达T600显卡)的前提下,让动画效果尽量丝滑不卡顿。
是否有内存泄漏?
一般情况下,如果大屏长时间运行,或者高屏 ws 推送或者 http 拉取数据,是有可能内存泄漏的。所以抱着有枣没枣打一竿子的心态,先使用 Chrome 的开发者工具「Performance monitor」看下实时 CPU 占用和 JS 大小。然后观察了一会,js 内存没有明显上涨,看起来是没有内存泄漏,保险起见,使用「Memory」对比了前后两次快照,发现也没有 Detached 游离对象。所以这条优化线路被 PASS 。
CSS动画 VS JavaScript动画 的性能
由于底部 3d 连线我是使用 canvas + requestAnimationFrame
实现的(核心代码如下):
startAnimation() {
this.clearCanvas();
this.layoutLine();
let lastTime = Date.now();
let dashOffset = 0;
let refreshInterval = 50;
const dashSpeed = 10;
const animation = () => {
const currentTime = Date.now();
const deltaTime = currentTime - lastTime;
if (deltaTime >= refreshInterval) {
this.canvasLineList.forEach(pathInfo => {
const { path, animate } = pathInfo;
if (animate) {
dashOffset = (dashOffset + dashSpeed * deltaTime / 1000) % (30);
path.dashOffset(-dashOffset);
lastTime = currentTime;
}
});
this.layer.batchDraw();
}
requestAnimationFrame(animation);
}
requestAnimationFrame(animation);
},
说白了就是使用 requestAnimationFrame
去不断更新每条连线的 dashOffset
从而达到虚线流动的动画效果。而这个操作是会加重电脑 CPU 负荷的(频繁地修改元素的属性会导致浏览器频繁地进行重绘和重排,这会占用较多的CPU资源。当频繁修改属性时,浏览器可能无法及时处理其他任务,导致页面卡顿或响应变慢。),尤其生产环境电脑还只有4G内存。
所以我的目光就聚焦到了连线动画上,把 canvas + requestAnimationFrame
方案改为了 svg + css3
动画。
改完后上了生产,在 ws 没有高屏高量的推送数据的情况下,动画效果看起来还行,算不上很丝滑吧但起码不卡了。
(想了解更多 CSS 动画与 JavaScript 动画的性能的比较,可以点击 这里的 MDN)
还需要优化?
本以为优化就到此结束了,哪知道还没过半小时又开始 Call 我,说不推送数据还好,一推送数据就又开始卡了,计时器会在推送数据时卡住,然后等推送完毕后过一会,突然从01s连续展示02、03、04直到05s然后继续每隔1s累加。看起来是线程卡死,导致 setInterval
延后执行了。
然后又阅读了代码,发现底部连线上的「系统」中的「系统名称」滚动和「系统切换」计时,都是一个个 setInterval
独立运行。这一个「系统」俩计时器,ws 推送时突然推过来20来个「系统」,不就40多个计时器了?
找到问题所在后就开始着手优化,首先是「系统名称」的滚动,改为了 js + 调整offsetWidth + css3
的方式代替 setInterval
,能减少 CPU 占用。
而对于「系统计时」功能,由于不管每个系统当前的切换时间相不相同,都会在 ws 推送时拿到各自最新时间然后后续定时器累加,所以干脆定一个一个统一计时器,然后用一个对象去记录所有需要计时的系统时间,然后统一在一个计时器中统一各自累加。这样能减少20多个计时器,也能提高系统性能。
最后我也用同样的方式优化了顶部扇形中「流程」轮播图的计时功能。
到此客户在生产环境下的大屏就不存在卡顿现象了,动画非常的丝滑。