Canvas- stroke 和 strokeWidth 导致性能下降

笔者在使用konva,在画布中绘制了大量数据点,并实现了画布的拖拽操作。但是,拖拽功能卡顿严重,在使用各种方法进行优化后,都没有显著提升。
在尝试删除绘制点的strokestrokeWidth属性后,拖拽性能显著提高,无明显卡顿现象。
现分享deepseek-v3对其的解释:


1. 原因分析

(1) strokestrokeWidth 增加了渲染复杂度

  • 当给元素添加 strokestrokeWidth 时,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

  • 如果描边不是必需的,可以移除 strokestrokeWidth 属性。
  • 例如:
    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. 总结

给元素添加 strokestrokeWidth 后拖拽卡顿的原因主要是描边增加了渲染复杂度和离屏渲染的开销。通过以下方法可以优化性能:

  1. 减小 strokeWidth
  2. 避免不必要的 stroke
  3. 使用 shadow 替代 stroke
  4. 合并图形或减少元素数量。
  5. 使用 batchDrawcache 优化渲染。

根据你的具体需求,选择合适的优化方法即可显著提升性能。

posted @ 2025-03-12 11:39  梧桐灯下江楚滢  阅读(88)  评论(0)    收藏  举报