Skip to main content

互动散点图与 20000 个节点

本实验的目的是通过渲染 20000 个圆形来展示 Konva 能处理的节点数量。每个圆形对鼠标悬停事件敏感,可以被拖动并放置。这个实验还很好地演示了事件委托,其中附加到舞台的单个事件处理器处理的是圆形事件。

说明: 将鼠标悬停在节点上以查看更多信息,然后在舞台上拖动和放置它们。

import Konva from 'konva';

// 创建舞台
const width = window.innerWidth;
const height = window.innerHeight;

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

// 添加节点到图层的函数
function addNode(obj, layer) {
  const node = new Konva.Circle({
    x: obj.x,
    y: obj.y,
    radius: 4,
    fill: obj.color,
    id: obj.id,
  });

  layer.add(node);
}

// 为所有圆形创建一个单独的图层
const circlesLayer = new Konva.Layer();
const tooltipLayer = new Konva.Layer();
const dragLayer = new Konva.Layer();

// 创建工具提示
const tooltip = new Konva.Label({
  opacity: 0.75,
  visible: false,
  listening: false,
});

tooltip.add(
  new Konva.Tag({
    fill: 'black',
    pointerDirection: 'down',
    pointerWidth: 10,
    pointerHeight: 10,
    lineJoin: 'round',
    shadowColor: 'black',
    shadowBlur: 10,
    shadowOffsetX: 10,
    shadowOffsetY: 10,
    shadowOpacity: 0.2,
  })
);

tooltip.add(
  new Konva.Text({
    text: '',
    fontFamily: 'Calibri',
    fontSize: 18,
    padding: 5,
    fill: 'white',
  })
);

tooltipLayer.add(tooltip);

// 构建数据
const data = [];
const colors = ['red', 'orange', 'cyan', 'green', 'blue', 'purple'];

for (let n = 0; n < 20000; n++) {
  const x = Math.random() * width;
  const y = height + Math.random() * 200 - 100 + (height / width) * -1 * x;
  data.push({
    x: x,
    y: y,
    id: n.toString(),
    color: colors[Math.round(Math.random() * 5)],
  });
}

// 将所有节点添加到单个图层
for (let n = 0; n < data.length; n++) {
  addNode(data[n], circlesLayer);
}

// 将所有图层添加到舞台
stage.add(circlesLayer);
stage.add(dragLayer);
stage.add(tooltipLayer);

// 处理事件
let originalLayer;

stage.on('mouseover mousemove dragmove', function (evt) {
  const node = evt.target;
  if (node === stage) {
    return;
  }
  if (node) {
    // 更新工具提示
    const mousePos = node.getStage().getPointerPosition();
    tooltip.position({
      x: mousePos.x,
      y: mousePos.y - 5,
    });
    tooltip
      .getText()
      .text('节点: ' + node.id() + ', 颜色: ' + node.fill());
    tooltip.show();
  }
});

stage.on('mouseout', function (evt) {
  tooltip.hide();
});

stage.on('mousedown', function (evt) {
  const shape = evt.target;
  if (shape) {
    originalLayer = shape.getLayer();
    shape.moveTo(dragLayer);
    // 手动触发拖动和放置
    shape.startDrag();
  }
});

stage.on('mouseup', function (evt) {
  const shape = evt.target;
  if (shape) {
    shape.moveTo(originalLayer);
  }
});