Canvas- stroke 和 strokeWidth 导致性能下降
笔者在使用konva,在画布中绘制了大量数据点,并实现了画布的拖拽操作。但是,拖拽功能卡顿严重,在使用各种方法进行优化后,都没有显著提升。
在尝试删除绘制点的stroke
和strokeWidth
属性后,拖拽性能显著提高,无明显卡顿现象。
现分享deepseek-v3
对其的解释:
1. 原因分析
(1) stroke
和 strokeWidth
增加了渲染复杂度
- 当给元素添加
stroke
和strokeWidth
时,Konva 需要额外绘制描边(stroke)。 - 描边的渲染比填充(fill)更复杂,尤其是当
strokeWidth
较大时,渲染开销会显著增加。 - 如果元素数量较多,每个元素都需要绘制描边,整体渲染压力会大幅增加。
(2) stroke
会导致额外的离屏渲染
- 在 Konva 中,描边通常需要离屏渲染(Offscreen Rendering)。离屏渲染是指将图形绘制到一个临时画布上,然后再将其复制到主画布。
- 离屏渲染会增加内存和计算开销,尤其是在频繁更新(如拖拽)时。
(3) strokeWidth
会影响命中检测
- Konva 的命中检测(用于判断鼠标是否在元素上)会考虑
strokeWidth
。 - 较大的
strokeWidth
会增加命中检测的计算量,尤其是在元素密集的区域。
(4) 浏览器绘图性能限制
- 浏览器在绘制描边时,需要执行更多的绘图指令(如抗锯齿、路径填充等)。
- 如果元素数量较多或描边较复杂,浏览器的绘图性能会显著下降。
2. 优化建议
(1) 减少 strokeWidth
- 如果
strokeWidth
较大,尝试将其减小。较小的strokeWidth
会降低渲染开销。 - 例如:
const config = { stroke: 'black', strokeWidth: 1, // 使用较小的描边宽度 };
(2) 避免不必要的 stroke
- 如果描边不是必需的,可以移除
stroke
和strokeWidth
属性。 - 例如:
const config = { fill: 'red', // 只使用填充 };
(3) 使用 shadow
替代 stroke
- 如果需要类似描边的效果,可以使用
shadow
属性。shadow
的性能通常比stroke
更好。 - 例如:
const config = { shadowColor: 'black', shadowBlur: 5, shadowOffset: { x: 2, y: 2 }, };
(4) 合并图形
- 如果多个元素具有相同的描边属性,可以将它们合并为一个图形。这样可以减少渲染次数。
- 例如,将多个
v-rect
合并为一个v-path
。
(5) 使用 batchDraw
- 在拖拽时,使用
batchDraw
批量绘制,而不是每次更新都重新渲染。 - 例如:
const stage = getStage(); stage.batchDraw();
(6) 启用 perfectDrawEnabled
- 将
perfectDrawEnabled
设置为false
,可以禁用 Konva 的精确绘制模式,提升性能。 - 例如:
const config = { perfectDrawEnabled: false, };
(7) 减少元素数量
- 如果元素数量过多,尝试减少元素数量。例如,通过合并元素或隐藏不必要的元素。
(8) 使用缓存
- 使用
cache
属性将元素缓存为位图,减少渲染开销。 - 例如:
const rect = new Konva.Rect({ width: 100, height: 100, fill: 'red', stroke: 'black', strokeWidth: 2, }); rect.cache(); // 缓存元素
3. 示例代码
以下是一个优化后的示例:
<template>
<v-stage :config="{ width: 500, height: 500 }">
<v-layer>
<v-group :config="{ x: 100, y: 50 }">
<v-rect
v-for="(rect, index) in rectangles"
:key="index"
:config="{
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
fill: 'red',
stroke: 'black',
strokeWidth: 1, // 使用较小的描边宽度
perfectDrawEnabled: false, // 禁用精确绘制
}"
@mousedown="handleMouseDown"
@mousemove="handleMouseMove"
@mouseup="handleMouseUp"
/>
</v-group>
</v-layer>
</v-stage>
</template>
<script setup>
import { ref } from 'vue';
import Konva from 'konva';
const rectangles = ref([
{ x: 10, y: 10, width: 50, height: 50 },
{ x: 80, y: 80, width: 50, height: 50 },
]);
const isDragging = ref(false);
const offset = ref({ x: 0, y: 0 });
const handleMouseMove = (event) => {
if (isDragging.value) {
const dx = event.evt.clientX - offset.value.x;
const dy = event.evt.clientY - offset.value.y;
rectangles.value.forEach((rect) => {
rect.x += dx;
rect.y += dy;
});
offset.value = { x: event.evt.clientX, y: event.evt.clientY };
const stage = getStage();
stage.batchDraw(); // 批量绘制
}
};
</script>
4. 总结
给元素添加 stroke
和 strokeWidth
后拖拽卡顿的原因主要是描边增加了渲染复杂度和离屏渲染的开销。通过以下方法可以优化性能:
- 减小
strokeWidth
。 - 避免不必要的
stroke
。 - 使用
shadow
替代stroke
。 - 合并图形或减少元素数量。
- 使用
batchDraw
和cache
优化渲染。
根据你的具体需求,选择合适的优化方法即可显著提升性能。