Is there an example or way of drawing a box around multiple objects on the konva canvas and by doing so selecting the objects which are inside this box with some kind of indicator around them?
Asked
Active
Viewed 7,929 times
4
-
[Edited] What you are talking about is often called a 'rubber rectangle'. Konvajs does not provide this. The draw-the-rectangle part is not hard to do though - on mousedown note the co-ords as the initial point, then as the mouse moves draw a rectangle from the initial point to the current point. Harder is that as the rectangle grows you need a hit-testing algorithm to see if the rubber-rectangle and current shapes touch, and highlight accordingly. Re hit-testing, if your shapes are rectangular or circular this is simple but needs DIY coding as there is no native collision detection. – Vanquished Wombat Nov 21 '17 at 09:11
-
thx for the hint, i'll give it a try!! – Andreas Zeiner Nov 21 '17 at 15:19
1 Answers
23
Here's a working snippet. Uses simple rect-overlap collision detection and corrects drag rect co-ordinates if user chooses to drag north or east. Should get you going.
// Set up the canvas and shapes
var s1 = new Konva.Stage({container: 'container1', width: 300, height: 200});
var layer1 = new Konva.Layer({draggable: false});
s1.add(layer1);
// draw a background rect to catch events.
var r1 = new Konva.Rect({x: 0, y: 0, width: 300, height: 200, fill: 'gold' })
layer1.add(r1)
// draw a rectangle to be used as the rubber area
var r2 = new Konva.Rect({x: 0, y: 0, width: 0, height: 0, stroke: 'red', dash: [2,2]})
r2.listening(false); // stop r2 catching our mouse events.
layer1.add(r2)
var shape1;
var shapeList=[];
for ( var i = 1; i <= 6; i = i + 1){
if (i % 2 == 0){
shape1 = new Konva.Rect({x: 100, y: 25 * i, width: 30, height: 25, fill: 'gold', stroke: 'black' })
shape1.on('click', function(e){
console.log(this.x() + ', ' + this.y())
})
}
if (i % 2 == 1){
shape1 = new Konva.Circle({x: 200, y: 30 * i, radius: 15, fill: 'red', stroke: 'black'})
}
shapeList.push(shape1); // add shape to our array of shapes to hit test later
shape1.listening(false); // stop shape interferring with mouse
layer1.add(shape1);
}
s1.draw() // First draw of canvas.
var posStart;
var posNow;
var mode = '';
function startDrag(posIn){
posStart = {x: posIn.x, y: posIn.y};
posNow = {x: posIn.x, y: posIn.y};
}
function updateDrag(posIn){
// update rubber rect position
posNow = {x: posIn.x, y: posIn.y};
var posRect = reverse(posStart,posNow);
r2.x(posRect.x1);
r2.y(posRect.y1);
r2.width(posRect.x2 - posRect.x1);
r2.height(posRect.y2 - posRect.y1);
r2.visible(true);
// run the collision check loop
for (i=0; i< shapeList.length; i = i + 1){
if (hitCheck(shapeList[i], r2)){
shapeList[i].stroke('lime'); // if we get a hit draw a lime stroke
}
else {
shapeList[i].stroke('black'); // otherwise leave stoke black.
}
}
s1.draw(); // redraw any changes.
}
// start the rubber drawing on mouse down.
r1.on('mousedown', function(e){
mode = 'drawing';
startDrag({x: e.evt.layerX, y: e.evt.layerY})
})
// update the rubber rect on mouse move - note use of 'mode' var to avoid drawing after mouse released.
r1.on('mousemove', function(e){
if (mode === 'drawing'){
updateDrag({x: e.evt.layerX, y: e.evt.layerY})
}
})
r1.on('mouseup', function(e){
mode = '';
r2.visible(false);
s1.draw();
})
function hitCheck(shape1, shape2){
var s1 = shape1.getClientRect(); // use this to get bounding rect for shapes other than rectangles.
var s2 = shape2.getClientRect();
// corners of shape 1
var X = s1.x;
var Y = s1.y
var A = s1.x + s1.width;
var B = s1.y + s1.height;
// corners of shape 2
var X1 = s2.x;
var A1 = s2.x + s2.width;
var Y1 = s2.y
var B1 = s2.y + s2.height;
// Simple overlapping rect collision test
if (A<X1 || A1<X || B<Y1 || B1<Y){
return false
}
else{
return true;
}
}
// reverse co-ords if user drags left / up
function reverse(r1, r2){
var r1x = r1.x, r1y = r1.y, r2x = r2.x, r2y = r2.y, d;
if (r1x > r2x ){
d = Math.abs(r1x - r2x);
r1x = r2x; r2x = r1x + d;
}
if (r1y > r2y ){
d = Math.abs(r1y - r2y);
r1y = r2y; r2y = r1y + d;
}
return ({x1: r1x, y1: r1y, x2: r2x, y2: r2y}); // return the corrected rect.
}
p
{
padding: 13px;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="https://cdn.rawgit.com/konvajs/konva/1.6.5/konva.min.js"></script>
<p>Click & drag on the background to capture shapes. Drag left-top to right-bottom or in reverse. Uses simple rect-overlap collision detection.
</p>
<div id='container1' style="display: inline-block; width: 300px, height: 200px; background-color: silver; overflow: hidden; position: relative;"></div>

Vanquished Wombat
- 9,075
- 5
- 28
- 67
-
Thanks for this amazing solution. I have only one issue. When I'm zooming my stage (https://pastebin.com/wpiHuPBK) the selective rectangle is not taking the correct coordinates. Any idea how I can solve that? Thank you – Bogdan Nicovski Dec 27 '19 at 21:57
-
1@BogdanNicovski - with the canvas, life is all about rectangles. For overlap math, you need to work everything back to a scale of 1:1, or apply the scaling to everything. Be consistent and all will be well. – Vanquished Wombat Dec 28 '19 at 14:18