当可能有很多形状时如何有效地使用事件监听器

How to use event listeners efficiently when there are potentially very many shapes

我的一个副项目具有以下特点:

我担心在所有形状上设置拖动开始、移动和结束侦听器的开销。如何避免在数千种形状上使用多个侦听器?

我有一个方法,我将 post 作为我自己的答案,但如果可以更有效地实现,请加入你自己的答案。

一个可能的答案是仅在放置形状的图层上使用侦听器。我可以有数千种形状,只需要 3 个听众。

该片段是一个游戏,玩家必须将每个圆圈拖到另一个圆圈上 - 当拖动的圆圈被放下时,它会被删除。只剩一圈时游戏结束

这里的要点是拖放的事件侦听器在 'layer' 上,而不是在每个圆 'shape' 上。 这意味着我只有这 3 个听众,而不管形状的数量。

这里特别有趣的是使用 Konva 的 Util.haveIntersection() 函数,该函数执行所有数学运算以检查一个形状的 clientRect 是否与另一个形状重叠,以及使用

layer.find('.connectable').each(function(shape) {...})

迭代特定形状子集的方法。

忽略重置功能,它全部包含在大约 100 well-spaced 行 JS 中。

let cClone = null, // will be a clone of the dragged circle.
  //  inDragOp = false,   // tells us that we are dragging.
  draggingShape = null, // points to the shape being dragged
  droppingShape = null,

  // add a stage
  stage = new Konva.Stage({
    container: 'container',
    width: $('#container').width(),
    height: $('#container').height()
  }),

  // make a layer to draw on
  layer = new Konva.Layer(),

  // make a connector line
  line = new Konva.Line({
    stroke: 'lime',
    strokeWidth: 4,
    points: []
  });

stage.add(layer);


// add shapes to the canvas
layer.add(line);

// first draw of shapes so far.
stage.draw();

// when the user starts to drag shape...
layer.on('dragstart', function(evt) {
  if (evt.target.hasName('connectable')) {

    draggingShape = evt.target;
    draggingShape.moveToTop();
    draggingShape.fill('cyan');

    cClone = draggingShape.clone(); // make a clone of the shape to remain as its ghost position indicator
    cClone.draggable(false); // clone not draggable   
    cClone.opacity(0.3); // make it ghostly
    cClone.name('');
    layer.add(cClone); // add to layer and draw.
    layer.batchDraw();

  }
});

// On each step of the movement
layer.on('dragmove', function(evt) {

  if (draggingShape) {

    // for performance, check if we are still overlapping the drop shape so we can avoid the need to examine all other poterntial overlaps.
    if (droppingShape && Konva.Util.haveIntersection(draggingShape.getClientRect(), droppingShape.getClientRect())) {
      line.points([cClone.x(), cClone.y(), droppingShape.x(), droppingShape.y()]);
      line.stroke('lime');
      layer.batchDraw(); // ... and redraw the layer.
      return false;
    }


    // Use Konvajs built-in rect overlap function to detect if the circles collide. Note this 
    // is not shape-based - it relies on comparing clientrect only!
    let cnt = 0;
    droppingShape = null;
    layer.find('.connectable').each(function(shape) {

      const overlapping = draggingShape === shape ? false : Konva.Util.haveIntersection(draggingShape.getClientRect(), shape.getClientRect());
      if (overlapping) { // hey - overlapping - so draw the connector line from the ghost to c2
        line.points([cClone.x(), cClone.y(), shape.x(), shape.y()]);
        line.stroke('lime');
        droppingShape = shape;
        droppingShape.fill('lime');
        layer.batchDraw(); // ... and redraw the layer.
        return false;
      } else {
        shape.fill('transparent');
        line.stroke('red');
        line.points([cClone.x(), cClone.y(), draggingShape.x(), draggingShape.y()])
      }
    })

    layer.batchDraw(); // ... and redraw the layer.
  }
});

// When the user completes the drag...
layer.on('dragend', function() {

  if (draggingShape) {
    if (droppingShape) {
      draggingShape.destroy();
    } else {
      draggingShape.position(cClone.position()); // put the dragged circle back where it started.
    }
    line.points([]); // no need for the line then !
    cClone.destroy(); // remove the clone
  }

  layer.batchDraw(); // redraw the layer.
  inDragOp = false; // clear the drag operation state

})


// reset to clean state.
$('#reset').on('click', function() {

  reset();

})

function reset() {

  layer.find('.connectable').each(function(shape) {
    shape.destroy();
  })

  let gridX = 3,
    gridY = 3,
    gap = 80;
  for (var i = 0; i < gridX; i++) {
    for (var j = 0; j < gridY; j++) {
      let c1 = new Konva.Circle({
        stroke: 'magenta',
        strokeWidth: 4,
        radius: 20,
        x: 50 + (i) * gap,
        y: 50 + (j) * gap,
        name: 'connectable',
        draggable: true
      });
      layer.add(c1);
    }
  }

  draggingShape = null;
  droppingShape = null;
  layer.batchDraw();
}

reset();
body {
  margin: 10;
  padding: 10;
  overflow: hidden;
  background-color: #f0f0f0;
}

#container {
  border: 1px solid silver;
  width: 500px;
  height: 300px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://unpkg.com/konva@^3/konva.min.js"></script>
<p>Drag and drop any circle over any other. </p>
<p>
  <button id='reset'>Reset</button>
</p>
<div id="container"></div>