当可能有很多形状时如何有效地使用事件监听器
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>
我的一个副项目具有以下特点:
- 舞台上的大量造型
- 每个形状都可以拖动
- 当拖动的形状与另一个形状发生碰撞时,我需要做一些事情。
我担心在所有形状上设置拖动开始、移动和结束侦听器的开销。如何避免在数千种形状上使用多个侦听器?
我有一个方法,我将 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>