HTML5 Canvas Drop Events
Konva does not support drop events. But you can write your own drop events detections. To detect drop target shape you have to move dragging object into another layer.
In this example you can see implementation of drop, dragenter, dragleave, dragover events.
Instructions: drag one shape over another. Or drag and drop one shape into another.
- Vanilla
- React
- Vue
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
const tempLayer = new Konva.Layer();
stage.add(layer);
stage.add(tempLayer);
const text = new Konva.Text({
fill: 'black',
});
layer.add(text);
let previousShape;
// create multiple stars
for (let i = 0; i < 10; i++) {
const star = new Konva.Star({
x: stage.width() * Math.random(),
y: stage.height() * Math.random(),
fill: 'blue',
numPoints: 10,
innerRadius: 20,
outerRadius: 25,
draggable: true,
name: 'star ' + i,
shadowOffsetX: 5,
shadowOffsetY: 5,
});
star.on('dragstart', () => {
star.moveTo(tempLayer);
text.text('Moving ' + star.name());
});
star.on('dragmove', (e) => {
const pos = stage.getPointerPosition();
const shape = layer.getIntersection(pos);
if (previousShape && shape) {
if (previousShape !== shape) {
// leave from old target
previousShape.fire('dragleave', { evt: e.evt }, true);
// enter new target
shape.fire('dragenter', { evt: e.evt }, true);
previousShape = shape;
} else {
previousShape.fire('dragover', { evt: e.evt }, true);
}
} else if (!previousShape && shape) {
previousShape = shape;
shape.fire('dragenter', { evt: e.evt }, true);
} else if (previousShape && !shape) {
previousShape.fire('dragleave', { evt: e.evt }, true);
previousShape = undefined;
}
});
star.on('dragend', (e) => {
const pos = stage.getPointerPosition();
const shape = layer.getIntersection(pos);
if (previousShape && previousShape !== shape) {
previousShape.fire('dragleave', { evt: e.evt }, true);
}
if (shape) {
shape.fire('drop', { evt: e.evt }, true);
}
previousShape = undefined;
star.moveTo(layer);
});
star.on('dragenter', () => {
star.fill('green');
text.text('dragenter ' + star.name());
});
star.on('dragleave', () => {
star.fill('blue');
text.text('dragleave ' + star.name());
});
star.on('dragover', () => {
text.text('dragover ' + star.name());
});
star.on('drop', () => {
star.fill('red');
text.text('drop ' + star.name());
});
layer.add(star);
}
import { Stage, Layer, Text, Star } from 'react-konva';
import { useState, useRef } from 'react';
const App = () => {
const [stars, setStars] = useState(() =>
Array.from({ length: 10 }, (_, i) => ({
id: i,
x: window.innerWidth * Math.random(),
y: window.innerHeight * Math.random(),
fill: 'blue',
name: `star ${i}`,
}))
);
const [message, setMessage] = useState('');
const previousShapeRef = useRef(null);
const mainLayerRef = useRef(null);
const tempLayerRef = useRef(null);
const updateStar = (id, attrs) => {
setStars((currentStars) =>
currentStars.map((star) =>
star.id === id ? { ...star, ...attrs } : star
)
);
};
const handleDragStart = (id, e) => {
const shape = e.target;
updateStar(id, { x: shape.x(), y: shape.y() });
shape.moveTo(tempLayerRef.current);
setMessage('Moving ' + shape.name());
};
const handleDragMove = (id, e) => {
const stage = e.target.getStage();
const pos = stage.getPointerPosition();
const shape = mainLayerRef.current.getIntersection(pos);
updateStar(id, { x: e.target.x(), y: e.target.y() });
if (previousShapeRef.current && shape) {
if (previousShapeRef.current !== shape) {
// leave from old target
previousShapeRef.current.fire('dragleave', { evt: e.evt }, true);
// enter new target
shape.fire('dragenter', { evt: e.evt }, true);
previousShapeRef.current = shape;
} else {
previousShapeRef.current.fire('dragover', { evt: e.evt }, true);
}
} else if (!previousShapeRef.current && shape) {
previousShapeRef.current = shape;
shape.fire('dragenter', { evt: e.evt }, true);
} else if (previousShapeRef.current && !shape) {
previousShapeRef.current.fire('dragleave', { evt: e.evt }, true);
previousShapeRef.current = undefined;
}
};
const handleDragEnd = (id, e) => {
const shape = e.target;
const stage = e.target.getStage();
const pos = stage.getPointerPosition();
const dropShape = mainLayerRef.current.getIntersection(pos);
if (
previousShapeRef.current &&
previousShapeRef.current !== dropShape
) {
previousShapeRef.current.fire('dragleave', { evt: e.evt }, true);
}
if (dropShape) {
dropShape.fire('drop', { evt: e.evt }, true);
}
updateStar(id, { x: shape.x(), y: shape.y() });
shape.moveTo(mainLayerRef.current);
previousShapeRef.current = undefined;
};
const handleDragEnter = (id, e) => {
updateStar(id, { fill: 'green' });
setMessage('dragenter ' + e.target.name());
};
const handleDragLeave = (id, e) => {
updateStar(id, { fill: 'blue' });
setMessage('dragleave ' + e.target.name());
};
const handleDragOver = (e) => {
setMessage('dragover ' + e.target.name());
};
const handleDrop = (id, e) => {
updateStar(id, { fill: 'red' });
setMessage('drop ' + e.target.name());
};
return (
<Stage width={window.innerWidth} height={window.innerHeight}>
<Layer ref={mainLayerRef}>
<Text text={message} fill="black" />
{stars.map((star) => (
<Star
key={star.id}
name={star.name}
x={star.x}
y={star.y}
numPoints={10}
innerRadius={20}
outerRadius={25}
fill={star.fill}
shadowOffsetX={5}
shadowOffsetY={5}
draggable
onDragStart={(e) => handleDragStart(star.id, e)}
onDragMove={(e) => handleDragMove(star.id, e)}
onDragEnd={(e) => handleDragEnd(star.id, e)}
onDragEnter={(e) => handleDragEnter(star.id, e)}
onDragLeave={(e) => handleDragLeave(star.id, e)}
onDragOver={handleDragOver}
onDrop={(e) => handleDrop(star.id, e)}
/>
))}
</Layer>
<Layer ref={tempLayerRef} />
</Stage>
);
};
export default App;
<template>
<v-stage :config="stageSize">
<v-layer ref="mainLayer">
<v-text :config="textConfig" />
<v-star
v-for="star in stars"
:key="star.id"
:config="getStarConfig(star)"
@dragstart="handleDragStart"
@dragmove="handleDragMove"
@dragend="handleDragEnd"
@dragenter="handleDragEnter"
@dragleave="handleDragLeave"
@dragover="handleDragOver"
@drop="handleDrop"
/>
</v-layer>
<v-layer ref="tempLayer" />
</v-stage>
</template>
<script setup>
import { ref, computed } from 'vue';
const stageSize = {
width: window.innerWidth,
height: window.innerHeight
};
const message = ref('');
const mainLayer = ref(null);
const tempLayer = ref(null);
const previousShape = ref(null);
const stars = ref(Array.from({ length: 10 }, (_, i) => ({
id: i,
x: window.innerWidth * Math.random(),
y: window.innerHeight * Math.random(),
fill: 'blue',
name: `star ${i}`
})));
const textConfig = computed(() => ({
text: message.value,
fill: 'black'
}));
const getStarConfig = (star) => ({
id: star.id,
name: star.name,
x: star.x,
y: star.y,
numPoints: 10,
innerRadius: 20,
outerRadius: 25,
fill: star.fill,
shadowOffsetX: 5,
shadowOffsetY: 5,
draggable: true
});
const handleDragStart = (e) => {
const shape = e.target;
shape.moveTo(tempLayer.value.getNode());
message.value = 'Moving ' + shape.attrs.name;
};
const handleDragMove = (e) => {
const stage = e.target.getStage();
const pos = stage.getPointerPosition();
const shape = mainLayer.value.getNode().getIntersection(pos);
if (previousShape.value && shape) {
if (previousShape.value !== shape) {
// leave from old target
previousShape.value.fire('dragleave', { evt: e.evt }, true);
// enter new target
shape.fire('dragenter', { evt: e.evt }, true);
previousShape.value = shape;
} else {
previousShape.value.fire('dragover', { evt: e.evt }, true);
}
} else if (!previousShape.value && shape) {
previousShape.value = shape;
shape.fire('dragenter', { evt: e.evt }, true);
} else if (previousShape.value && !shape) {
previousShape.value.fire('dragleave', { evt: e.evt }, true);
previousShape.value = null;
}
};
const handleDragEnd = (e) => {
const shape = e.target;
const stage = e.target.getStage();
const pos = stage.getPointerPosition();
const dropShape = mainLayer.value.getNode().getIntersection(pos);
if (previousShape.value && previousShape.value !== dropShape) {
previousShape.value.fire('dragleave', { evt: e.evt }, true);
}
if (dropShape) {
dropShape.fire('drop', { evt: e.evt }, true);
}
shape.moveTo(mainLayer.value.getNode());
previousShape.value = null;
};
const handleDragEnter = (e) => {
const shape = e.target;
shape.fill('green');
message.value = 'dragenter ' + shape.attrs.name;
};
const handleDragLeave = (e) => {
const shape = e.target;
shape.fill('blue');
message.value = 'dragleave ' + shape.attrs.name;
};
const handleDragOver = (e) => {
message.value = 'dragover ' + e.target.attrs.name;
};
const handleDrop = (e) => {
const shape = e.target;
shape.fill('red');
message.value = 'drop ' + shape.attrs.name;
};
</script>