Drag and Drop Collision Detection Demo
How to find overlapping objects on the canvas?
In this demo we will use simple collision detection to highlight intersected objects. For simplicity we will use just bounding boxes to detect collision.
Red borders are used to show bounding boxes.
- Vanilla
- React
- Vue
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); function createShape() { var group = new Konva.Group({ x: Math.random() * width, y: Math.random() * height, draggable: true, }); var shape = new Konva.Rect({ width: 30 + Math.random() * 30, height: 30 + Math.random() * 30, fill: 'grey', rotation: 360 * Math.random(), name: 'fillShape', }); group.add(shape); var boundingBox = shape.getClientRect({ relativeTo: group }); var box = new Konva.Rect({ x: boundingBox.x, y: boundingBox.y, width: boundingBox.width, height: boundingBox.height, stroke: 'red', strokeWidth: 1, }); group.add(box); return group; } for (var i = 0; i < 10; i++) { layer.add(createShape()); } layer.on('dragmove', function (e) { var target = e.target; var targetRect = e.target.getClientRect(); layer.children.forEach(function (group) { // do not check intersection with itself if (group === target) { return; } if (haveIntersection(group.getClientRect(), targetRect)) { group.findOne('.fillShape').fill('red'); } else { group.findOne('.fillShape').fill('grey'); } }); }); function haveIntersection(r1, r2) { return !( r2.x > r1.x + r1.width || r2.x + r2.width < r1.x || r2.y > r1.y + r1.height || r2.y + r2.height < r1.y ); }
import { Stage, Layer, Group, Rect } from 'react-konva'; import { useState } from 'react'; const createInitialShapes = () => { const shapes = []; for (let i = 0; i < 10; i++) { const width = 30 + Math.random() * 30; const height = 30 + Math.random() * 30; const rotation = 360 * Math.random(); // calculate bounding box for rotated rectangle const radians = (rotation * Math.PI) / 180; const cos = Math.cos(radians); const sin = Math.sin(radians); // calculate corners of the rectangle const corners = [ { x: 0, y: 0 }, { x: width, y: 0 }, { x: width, y: height }, { x: 0, y: height } ].map(point => ({ x: point.x * cos - point.y * sin, y: point.x * sin + point.y * cos })); // find bounding box dimensions const minX = Math.min(...corners.map(p => p.x)); const maxX = Math.max(...corners.map(p => p.x)); const minY = Math.min(...corners.map(p => p.y)); const maxY = Math.max(...corners.map(p => p.y)); shapes.push({ id: i, x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight, rotation, width, height, fill: 'grey', box: { x: minX, y: minY, width: maxX - minX, height: maxY - minY } }); } return shapes; }; const haveIntersection = (r1, r2) => { return !( r2.x > r1.x + r1.width || r2.x + r2.width < r1.x || r2.y > r1.y + r1.height || r2.y + r2.height < r1.y ); }; const App = () => { const [shapes, setShapes] = useState(createInitialShapes()); const handleDragMove = (e, id) => { const target = e.target; const targetRect = target.getClientRect(); setShapes(shapes.map(shape => { if (shape.id === id) { return shape; } const shapeGroup = target.parent.parent.findOne(`#group-${shape.id}`); if (!shapeGroup) return shape; const isIntersecting = haveIntersection( shapeGroup.getClientRect(), targetRect ); return { ...shape, fill: isIntersecting ? 'red' : 'grey' }; })); }; const handleDragEnd = (e, id) => { setShapes(shapes.map(shape => shape.id === id ? { ...shape, x: e.target.x(), y: e.target.y() } : shape )); }; return ( <Stage width={window.innerWidth} height={window.innerHeight}> <Layer> {shapes.map((shape) => ( <Group key={shape.id} id={`group-${shape.id}`} x={shape.x} y={shape.y} draggable onDragMove={(e) => handleDragMove(e, shape.id)} onDragEnd={(e) => handleDragEnd(e, shape.id)} > <Rect width={shape.width} height={shape.height} fill={shape.fill} rotation={shape.rotation} name="fillShape" /> <Rect x={shape.box.x} y={shape.box.y} width={shape.box.width} height={shape.box.height} stroke="red" strokeWidth={1} /> </Group> ))} </Layer> </Stage> ); }; export default App;
<template> <v-stage :config="stageSize"> <v-layer ref="layer"> <template v-for="shape in shapes" :key="shape.id"> <v-group :config="{ x: shape.x, y: shape.y, draggable: true, id: `group-${shape.id}` }" @dragmove="(e) => handleDragMove(e, shape.id)" @dragend="(e) => handleDragEnd(e, shape.id)" > <v-rect :config="{ width: shape.width, height: shape.height, fill: shape.fill, rotation: shape.rotation, name: 'fillShape' }" /> <v-rect :config="{ x: shape.box.x, y: shape.box.y, width: shape.box.width, height: shape.box.height, stroke: 'red', strokeWidth: 1 }" /> </v-group> </template> </v-layer> </v-stage> </template> <script setup> import { ref } from 'vue'; const stageSize = { width: window.innerWidth, height: window.innerHeight }; const createInitialShapes = () => { const shapes = []; for (let i = 0; i < 10; i++) { const width = 30 + Math.random() * 30; const height = 30 + Math.random() * 30; const rotation = 360 * Math.random(); // calculate bounding box for rotated rectangle const radians = (rotation * Math.PI) / 180; const cos = Math.cos(radians); const sin = Math.sin(radians); // calculate corners of the rectangle const corners = [ { x: 0, y: 0 }, { x: width, y: 0 }, { x: width, y: height }, { x: 0, y: height } ].map(point => ({ x: point.x * cos - point.y * sin, y: point.x * sin + point.y * cos })); // find bounding box dimensions const minX = Math.min(...corners.map(p => p.x)); const maxX = Math.max(...corners.map(p => p.x)); const minY = Math.min(...corners.map(p => p.y)); const maxY = Math.max(...corners.map(p => p.y)); shapes.push({ id: i, x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight, rotation, width, height, fill: 'grey', box: { x: minX, y: minY, width: maxX - minX, height: maxY - minY } }); } return shapes; }; const shapes = ref(createInitialShapes()); const haveIntersection = (r1, r2) => { return !( r2.x > r1.x + r1.width || r2.x + r2.width < r1.x || r2.y > r1.y + r1.height || r2.y + r2.height < r1.y ); }; const handleDragMove = (e, id) => { const target = e.target; const layer = target.getLayer(); const targetRect = target.getClientRect(); shapes.value = shapes.value.map(shape => { if (shape.id === id) { return shape; } const shapeGroup = layer.findOne(`#group-${shape.id}`); if (!shapeGroup) return shape; const isIntersecting = haveIntersection( shapeGroup.getClientRect(), targetRect ); return { ...shape, fill: isIntersecting ? 'red' : 'grey' }; }); }; const handleDragEnd = (e, id) => { shapes.value = shapes.value.map(shape => shape.id === id ? { ...shape, x: e.target.x(), y: e.target.y() } : shape ); }; </script>