Skip to main content

使用 Konva 的缩放压力测试

这是一个压力测试演示,用于同时选择和缩放多个形状。

该演示使用了两个核心的 Konva 特性来提升性能:

1. 图层

正在缩放的形状被移动到另一个图层(另一个画布元素)。因此,当您缩放选定的形状时,我们不需要重新绘制其他形状。

2. 缓存

select 时,我将所有选定的形状移动到一个组中并缓存该组。缓存操作将组转换为位图图像。在屏幕上重新绘制这样的组速度更快。

说明:尝试选择几个形状并缩放/旋转它们。

import Konva from 'konva';

// 首先我们需要创建一个舞台
var width = window.innerWidth;
var height = window.innerHeight;

var stage = new Konva.Stage({
  container: 'container',
  width: width,
  height: height,
});

// 所有形状的图层
var layer = new Konva.Layer();
stage.add(layer);
for (var i = 0; i < 10000; i++) {
  var shape = new Konva.Circle({
    x: Math.random() * window.innerWidth,
    y: Math.random() * window.innerHeight,
    radius: 10,
    name: 'shape',
    fill: Konva.Util.getRandomColor(),
  });
  layer.add(shape);
}

// 用于转换组的顶部图层
var topLayer = new Konva.Layer();
stage.add(topLayer);

var group = new Konva.Group({
  draggable: true,
});
topLayer.add(group);

var tr = new Konva.Transformer();
topLayer.add(tr);

// 添加一个新特性,让我们添加绘制选择矩形的能力
var selectionRectangle = new Konva.Rect({
  fill: 'rgba(0,0,255,0.5)',
  visible: false,
});
topLayer.add(selectionRectangle);

var x1, y1, x2, y2;
stage.on('mousedown touchstart', (e) => {
  // 如果我们在变换器上按下鼠标,则不执行任何操作
  if (e.target.getParent() === tr) {
    return;
  }
  // 如果我们在组上按下鼠标,则不执行任何操作
  if (e.target.parent === group) {
    return;
  }
  x1 = stage.getPointerPosition().x;
  y1 = stage.getPointerPosition().y;
  x2 = stage.getPointerPosition().x;
  y2 = stage.getPointerPosition().y;

  selectionRectangle.setAttrs({
    x: x1,
    y: y1,
    width: 0,
    height: 0,
    visible: true,
  });

  // 将旧选择移回原始图层
  group.children.slice().forEach((shape) => {
    const transform = shape.getAbsoluteTransform();
    shape.moveTo(layer);
    shape.setAttrs(transform.decompose());
  });
  // 重置组转换
  group.setAttrs({
    x: 0,
    y: 0,
    scaleX: 1,
    scaleY: 1,
    rotation: 0,
  });
  group.clearCache();
});

stage.on('mousemove touchmove', () => {
  // 如果我们没有开始选择,则不执行任何操作
  if (!selectionRectangle.visible()) {
    return;
  }
  x2 = stage.getPointerPosition().x;
  y2 = stage.getPointerPosition().y;

  selectionRectangle.setAttrs({
    x: Math.min(x1, x2),
    y: Math.min(y1, y2),
    width: Math.abs(x2 - x1),
    height: Math.abs(y2 - y1),
  });
});

stage.on('mouseup touchend', () => {
  // 如果我们没有开始选择,则不执行任何操作
  if (!selectionRectangle.visible()) {
    return;
  }
  // 更新可见性,延时处理,这样我们可以在点击事件中检查
  setTimeout(() => {
    selectionRectangle.visible(false);
  });

  var shapes = stage.find('.shape');
  var box = selectionRectangle.getClientRect();

  // 为了更好的性能,移除所有子元素
  layer.removeChildren();

  // 然后检查交集并将所有形状添加到正确的容器中
  shapes.forEach((shape) => {
    var intersected = Konva.Util.haveIntersection(
      box,
      shape.getClientRect()
    );
    if (intersected) {
      group.add(shape);
      shape.stroke('blue');
    } else {
      layer.add(shape);
      shape.stroke(null);
    }
  });

  if (group.children.length) {
    tr.nodes([group]);
    group.cache();
  } else {
    tr.nodes([]);
    group.clearCache();
  }
});

// 点击应该选择/取消选择形状
stage.on('click tap', function (e) {
  // 如果我们正在用矩形选择,则不执行任何操作
  if (selectionRectangle.visible()) {
    return;
  }

  // 如果点击空白区域 - 移除所有选择
  if (e.target === stage) {
    tr.nodes([]);
    return;
  }
});