前端使用 Konva 实现可视化设计器(20)- 性能优化、UI 美化

@xachary 2024-09-09 15:33:01 阅读 73

这一章主要分享一下使用 Konva 遇到的性能优化问题,并且介绍一下 UI 美化的思路。

至少有 2 位小伙伴积极反馈,发现本示例有明显的性能问题,一是内存溢出问题,二是卡顿的问题,在这里感谢大家的提醒。

请大家动动小手,给我一个免费的 Star 吧~

大家如果发现了 Bug,欢迎来提 Issue 哟~

github源码

gitee源码

示例地址

性能优化

内存溢出

根据官方文档 Konva Class: Node 的说明:

remove(): remove a node from parent, but don’t destroy. You can reuse the node later.

destroy(): remove and destroy a node. Kill it and delete forever! You should not reuse node after destroy().

If the node is a container (Group, Stage or Layer) it will destroy all children too.

在本示例之前的版本中,只使用 remove() 是不正确的,只使用 remove,每次 redraw 都产生巨量的实例没有被清除,也就是内存溢出了,导致 JS heap size 随随便便干到几个 GB。

【简单判断内存溢出】

前往:Chrome -> Console 面板 -> 左侧更多 -> Performance monitor -> JS heap size

如果内存只升不降,基本可以认为内存溢出了。

在本示例中,大部分图形实例都是用完即弃的,所以大部分的 remove 都替换为 destory 后,JS heap size 将基本维持在几十上百 MB(根据内容复杂度)。

这里提个醒,除了使用 remove 的时候要注意,还有个容易忽略的 API 要注意,就是 Stage、Layer、Group 的 removeChildren(),如果子节点不再有用,建议先遍历子节点分别 destroy 一下。

初始状态,如下:

在这里插入图片描述

卡顿

在本示例之前的版本中,只要画面需要变化,都是重新 redraw 所有图形,这导致加载的素材过多的时候,交互会产生明显的卡顿,尤其是加载 gif 的时候,每一帧都会 redraw 一次。

因此,redraw 必须是可以选择性 draw 每一层 layer 的,主要调整如下:

<code>// 重绘(可选择)

redraw(drawNames?: string[]) {

const all = [

Draws.BgDraw.name, // 更新背景

Draws.LinkDraw.name, // 更新连线

Draws.AttractDraw.name, // 更新磁贴

Draws.RulerDraw.name, // 更新比例尺

Draws.RefLineDraw.name, // 更新参考线

Draws.PreviewDraw.name, // 更新预览

Draws.ContextmenuDraw.name // 更新右键菜单

]

if (Array.isArray(drawNames) && !this.debug) {

// 选择性 draw 也要保持顺序

for (const name of all) {

if (drawNames.includes(name)) {

this.draws[name].draw()

}

}

} else {

for (const name of all) {

this.draws[name].draw()

}

}

}

这里有几点细节考虑:

1、传哪些 drawNames 就 redraw 哪些 draw 的 group,除非当时是调试模式。

2、不传 drawNames 就全 redraw。

3、redraw 要按 all 的顺序执行。

举例:

拖动画布的时候:

this.render.redraw([Draws.BgDraw.name, Draws.RulerDraw.name, Draws.PreviewDraw.name])

因为这个交互只影响了 背景、比例尺、预览的 draw。

放大缩小的时候:

this.render.redraw([

Draws.BgDraw.name,

Draws.LinkDraw.name,

Draws.RulerDraw.name,

Draws.RefLineDraw.name,

Draws.PreviewDraw.name

])

此时影响的 draw 就比较多了。

根据不同交互的特点,做必要的 redraw 处理,就可以很好的提高交互性能,减少卡顿。

UI 美化

之前的重心都放在画布的交互上,界面得过且过就行了。

现在基础架构基本稳定了,是应该美化一下丑陋的 UI 了,简单美化后:

在这里插入图片描述

Naive UI

为了快速美化,这里用 Naive UI,比较清爽。

主要美化了一下 头部 和 素材 栏:

src/components/main-headersrc/components/asset-bar

这里就不贴具体代码了,比较简单。

mitt - Emitter

之前是通过配置式,传入一些 方法 当作事件的 handler,没法动态订阅,太不方便了。

这里改造了一下 Render,使用 mitt 给它赋予 Emitter 能力:

<code>// 略

import mitt, { type Emitter } from 'mitt'

// 略

export class Render {

// 略

protected emitter: Emitter<Types.RenderEvents> = mitt()

on: Emitter<Types.RenderEvents>['on']

off: Emitter<Types.RenderEvents>['off']

emit: Emitter<Types.RenderEvents>['emit']

// 略

constructor(stageEle: HTMLDivElement, config: Types.RenderConfig) {

// 略

this.on = this.emitter.on.bind(this.emitter)

this.off = this.emitter.off.bind(this.emitter)

this.emit = this.emitter.emit.bind(this.emitter)

// 略

}

}

在外面的组件里,通过 render 实例,就可以方便订阅事件,例如:

props.render?.on('selection-change', (nodes: Konva.Node[]) => {

selection.value = nodes

})

Thanks watching~

More Stars please!勾勾手指~

源码

gitee源码

示例地址



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。