How to drag and drop DOM image into the canvas
In this demo we will demonstrate how drop DOM element that is placed outside of canvas into the stage.
The first image is a DOM image. We can use HTML drag and drop to make it draggable.
You will need some extra step if you need to enable drag&drop for DOM element on touch devices. You can read here for more info.
Instructions: drag&drop yoda into the canvas.
- Vanilla
- React
- Vue
import Konva from 'konva';
const width = window.innerWidth;
const height = window.innerHeight;
// Add DOM elements to render outside the container
document.getElementById('container').insertAdjacentHTML(
'beforebegin',
`
<p>Drag&drop yoda into the grey area.</p>
<div id="drag-items">
<img src="https://konvajs.org/assets/yoda.jpg" draggable="true" style="height: 100px; margin: 5px;" />
<img src="https://konvajs.org/assets/darth-vader.jpg" draggable="true" style="height: 100px; margin: 5px;" />
</div>
`
);
// Style the container with grey background
document.getElementById('container').style.backgroundColor = 'rgba(0, 0, 0, 0.1)';
// Create stage and layer
const stage = new Konva.Stage({
container: 'container',
width: width,
height: height - 150, // leave space for the DOM elements
});
const layer = new Konva.Layer();
stage.add(layer);
// Track URL of the dragging element
let itemURL = '';
document
.getElementById('drag-items')
.addEventListener('dragstart', function (e) {
itemURL = e.target.src;
});
// Handle dragover on container
const container = stage.container();
container.addEventListener('dragover', function (e) {
e.preventDefault(); // important - must prevent default behavior
});
// Handle drop on container
container.addEventListener('drop', function (e) {
e.preventDefault();
// Register the pointer position manually since this is a DOM event
stage.setPointersPositions(e);
// Load the image and add it to the layer
Konva.Image.fromURL(itemURL, function (image) {
// Calculate appropriate size based on image dimensions
const img = image.image();
const maxDimension = 100;
let width = img.width;
let height = img.height;
if (width > height) {
height = (height / width) * maxDimension;
width = maxDimension;
} else {
width = (width / height) * maxDimension;
height = maxDimension;
}
image.size({
width: width,
height: height
});
layer.add(image);
image.position(stage.getPointerPosition());
image.draggable(true);
});
});
import { useState, useRef, useEffect } from 'react';
import { Stage, Layer, Image } from 'react-konva';
import { useImage } from 'react-konva-utils';
const DragItem = ({ src, onDragStart, onDragEnd }) => {
return (
<img
src={src}
draggable={true}
style={{ height: '100px', margin: '5px' }}
onDragStart={() => onDragStart(src)}
onDragEnd={onDragEnd}
/>
);
};
const App = () => {
const [images, setImages] = useState([]);
const stageRef = useRef(null);
const dragImageSrc = useRef('');
const nextImageId = useRef(1);
const handleDragStart = (src) => {
dragImageSrc.current = src;
};
const handleDragSourceEnd = () => {
dragImageSrc.current = '';
};
const handleDragOver = (e) => {
e.preventDefault(); // prevent default behavior
};
const handleDrop = (e) => {
e.preventDefault();
const src = dragImageSrc.current;
dragImageSrc.current = '';
if (!src || !stageRef.current) return;
// Get stage and pointer position
const stage = stageRef.current;
// Register the pointer position manually since this is a DOM event
stage.setPointersPositions(e);
const position = stage.getPointerPosition();
const id = `image-${nextImageId.current++}`;
// Add new image to the list
setImages(currentImages => [
...currentImages,
{
src,
x: position.x,
y: position.y,
id
}
]);
};
const handleImageDragEnd = (id, e) => {
const { x, y } = e.target.position();
setImages(currentImages => currentImages.map(img =>
img.id === id ? { ...img, x, y } : img
));
};
return (
<div>
<p>Drag&drop yoda into the grey area.</p>
<div style={{ marginBottom: '10px' }}>
<DragItem
src="https://konvajs.org/assets/yoda.jpg"
onDragStart={handleDragStart}
onDragEnd={handleDragSourceEnd}
/>
<DragItem
src="https://konvajs.org/assets/darth-vader.jpg"
onDragStart={handleDragStart}
onDragEnd={handleDragSourceEnd}
/>
</div>
<div
onDragOver={handleDragOver}
onDrop={handleDrop}
style={{ backgroundColor: 'rgba(0, 0, 0, 0.1)' }}
>
<Stage
width={window.innerWidth}
height={window.innerHeight - 150}
ref={stageRef}
>
<Layer>
{images.map((img) => (
<KonvaImage
key={img.id}
src={img.src}
x={img.x}
y={img.y}
draggable
onDragEnd={(e) => handleImageDragEnd(img.id, e)}
/>
))}
</Layer>
</Stage>
</div>
</div>
);
};
// Separate component for Konva Image with proper loading
const KonvaImage = ({ src, x, y, draggable, onDragEnd }) => {
const [image] = useImage(src);
if (!image) return null;
// Calculate appropriate size
const maxDimension = 100;
let width = image.width;
let height = image.height;
if (width > height) {
height = (height / width) * maxDimension;
width = maxDimension;
} else {
width = (width / height) * maxDimension;
height = maxDimension;
}
return (
<Image
image={image}
x={x}
y={y}
width={width}
height={height}
draggable={draggable}
onDragEnd={onDragEnd}
/>
);
};
export default App;
<template>
<div>
<p>Drag&drop yoda into the grey area.</p>
<div style="margin-bottom: 10px;">
<img
src="https://konvajs.org/assets/yoda.jpg"
draggable="true"
style="height: 100px; margin: 5px;"
@dragstart="handleDragStart('https://konvajs.org/assets/yoda.jpg')"
/>
<img
src="https://konvajs.org/assets/darth-vader.jpg"
draggable="true"
style="height: 100px; margin: 5px;"
@dragstart="handleDragStart('https://konvajs.org/assets/darth-vader.jpg')"
/>
</div>
<div
@dragover.prevent
@drop="handleDrop"
style="background-color: rgba(0, 0, 0, 0.1);"
>
<v-stage
ref="stageRef"
:config="stageConfig"
>
<v-layer>
<v-image
v-for="img in images"
:key="img.id"
:config="getImageConfig(img)"
/>
</v-layer>
</v-stage>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue';
const stageConfig = {
width: window.innerWidth,
height: window.innerHeight - 150
};
const stageRef = ref(null);
const images = ref([]);
const dragImageSrc = ref('');
const loadedImages = ref({});
// Handle drag start to track the image being dragged
const handleDragStart = (src) => {
dragImageSrc.value = src;
};
// Handle drop event to add image to stage
const handleDrop = (e) => {
e.preventDefault();
if (!dragImageSrc.value || !stageRef.value) return;
// Get stage reference and register pointer positions
const stage = stageRef.value.getNode();
stage.setPointersPositions(e);
const position = stage.getPointerPosition();
// Load the image if not loaded already
if (!loadedImages.value[dragImageSrc.value]) {
const img = new Image();
img.onload = () => {
loadedImages.value = {
...loadedImages.value,
[dragImageSrc.value]: img
};
// Add image to the stage
addImageToStage(dragImageSrc.value, position);
};
img.src = dragImageSrc.value;
} else {
// Image already loaded, add it to stage
addImageToStage(dragImageSrc.value, position);
}
};
// Helper to add image to stage
const addImageToStage = (src, position) => {
images.value.push({
src: src,
x: position.x,
y: position.y,
id: Date.now().toString()
});
};
// Configure image properties
const getImageConfig = (img) => {
const image = loadedImages.value[img.src];
if (!image) return { x: img.x, y: img.y, draggable: true };
// Calculate appropriate size
const maxDimension = 100;
let width = image.width;
let height = image.height;
if (width > height) {
height = (height / width) * maxDimension;
width = maxDimension;
} else {
width = (width / height) * maxDimension;
height = maxDimension;
}
return {
image: image,
x: img.x,
y: img.y,
width: width,
height: height,
draggable: true
};
};
</script>