這一章主要分享一下使用 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 的,主要調整如下:
// 重繪(可選擇)
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-header
- src/components/asset-bar
這裡就不貼具體程式碼了,比較簡單。
mitt - Emitter
之前是透過配置式,傳入一些 方法 當作事件的 handler,沒法動態訂閱,太不方便了。
這裡改造了一下 Render,使用 mitt 給它賦予 Emitter 能力:
// 略
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原始碼
示例地址