Binding drag and drop elements in JavaScript
I am trying to use drag and drop to move images from one <div>
to another.
Currently, I can move images anywhere in the destination <div>
, but I really want the images to be linked when deleted. Ideally, they could squeeze together on either side (not just the bottom or right, for example).
I've tried several different things (including usage <canvas>
) and it didn't work.
This is what I have so far:
var clone;
var offsetx = null;
var offsety = null;
var isClone = false;
function allowDrop(ev) {
ev.preventDefault();
}
function drag(ev) {
offsetx = ev.target.offsetLeft - event.clientX;
offsety = ev.target.offsetTop - event.clientY;
ev.dataTransfer.setData("text", ev.target.id);
}
function dropTrash(ev) {
ev.preventDefault();
var data = ev.dataTransfer.getData("text");
var remove = document.getElementById(data);
remove.parentNode.removeChild(remove);
}
function drop(ev) {
ev.preventDefault();
var data = ev.dataTransfer.getData("text");
}
function dropClone(ev) {
ev.preventDefault();
var data = ev.dataTransfer.getData("text");
var num = Math.random() * (1000 - 1) + 1;
isClone = true;
clone = document.getElementById(data).cloneNode(true);
clone.id = "newId" + num.toString();
clone.style.position = "absolute";
clone.style.left = (event.clientX+offsetx)+"px";
clone.style.top = (event.clientY+offsety)+"px";
ev.target.appendChild(clone);
}
html, body {
height: 100%;
padding: 0;
margin: 0;
}
div {
width: 50%;
height: 50%;
float: left;
}
#div1 {
background: #DDD;
}
#div2 {
background: #AAA;
}
#div3 {
background: #777;
}
#div4 {
background: #444;
}
#imgDiv {
width: 611px;
height: 324px;
border: 5px solid #DDD;
}
<div id="div1">
</div>
<div id="div2">
</div>
<div id="div3" ondrop="dropTrash(event)" ondragover="allowDrop(event)">
<img id="drag1" src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/18/Bartagame_fcm.jpg/1200px-Bartagame_fcm.jpg" draggable="true" ondragstart="drag(event)" width="105" height="105">
<img id="drag2" src="http://www.earthtimes.org/newsimage/lizard_Ngo_Van_Tri_big_281.jpg" draggable="true" ondragstart="drag(event)" width="105" height="105">
</div>
<div id="div4">
<div align="center" id="imgDiv" ondrop="dropClone(event)" ondragover="allowDrop(event)"></div>
</div>
source to share
When you start dragging an image, you need to keep the cursor position relative to that particular image.
There are several position properties in MouseEvent to help you calculate this, but if browser support is not an issue, I have for MouseEvent.offsetX and MouseEvent.offsetY . From the docs:
The
offsetX
/offsetY
read-only property of the MouseEvent interface provides the offset at the coordinateX
/Y
mouse pointer between this event and the padding edge of the target node.
So, dragstart
you just do:
x = e.offsetX;
y = e.offsetY;
Then, when you drop the image in yours, let it call it canvas (note the italics, as this is not an element <canvas>
, but any other element that you use as the dropout area <div>
in this particular example), you need to know the position of the cursor relative to of this canvas, so you might think you can use offsetX
and again offsetY
, and you're partially right. This will give you the expected value if you snap an image on the canvas itself, but there may be other images in it, and you can flush the current one on top of another, getting offsetX
and offsetY
relative instead.
What you can do is use MouseEvent.pageX
and MouseEvent.pageY
, and from that value, subtract the position (top left corner) of that canvas element, which you can get from HTMLElement.offsetLeft and HTMLElement.offsetTop :
e.pageX - imageCanvas.offsetLeft;
e.pageY - imageCanvas.offsetTop;
With this, you get the position of the cursor relative to the canvas element.
Now you need to subtract the value X
and Y
that you have stored on dragstart
, and it will give you the value left
and top
the upper-left corner of the drag image relative to the canvas element:
image.style.left = (e.pageX - imagesCanvas.offsetLeft - x) + 'px';
image.style.top = (e.pageY - imagesCanvas.offsetTop - y) + 'px';
Taken together, it will look like this:
let x;
let y;
let currentTarget = null;
let cloneElement = false;
function startDrag(e, clone) {
const target = e.target;
if (target.tagName === 'IMG') {
x = e.offsetX;
y = e.offsetY;
currentTarget = target;
cloneElement = clone;
}
}
function cloneImage(e) {
startDrag(e, true);
}
function moveImage(e) {
startDrag(e, false);
}
function removeImage(e) {
if (!cloneElement) {
currentTarget.remove();
}
}
function stickImage(e) {
const image = cloneElement ? currentTarget.cloneNode(true) : currentTarget;
imagesCanvas.appendChild(image);
// + 1 for the border
image.style.left = (e.pageX - imagesCanvas.offsetLeft - x + 1) + 'px';
image.style.top = (e.pageY - imagesCanvas.offsetTop - y + 1) + 'px';
currentTarget = null;
}
function allowDrag(e) {
e.preventDefault();
}
// Bind event listeners:
const imagesBarElement = document.getElementById('imagesBar');
const imagesCanvasElement = document.getElementById('imagesCanvas');
document.addEventListener('dragenter', allowDrag);
document.addEventListener('dragover', allowDrag);
imagesBarElement.addEventListener('dragstart', cloneImage);
imagesBarElement.addEventListener('drop', removeImage);
imagesCanvasElement.addEventListener('dragstart', moveImage);
imagesCanvasElement.addEventListener('drop', stickImage);
body {
margin: 0;
font-size: 0;
display: flex;
flex-direction: column;
height: 100vh;
user-select: none;
}
img {
width: 100px;
height: 100px;
}
#imagesBar {
height: 100px;
border-bottom: 1px solid #CCC;
padding: 10px 0;
}
#imagesBar > img {
margin: 0 0 0 10px;
}
#imagesCanvas {
position: relative;
background: #EEE;
flex-grow: 1;
overflow: hidden;
}
#imagesCanvas > img {
position: absolute;
}
<div id="imagesBar">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/18/Bartagame_fcm.jpg/1200px-Bartagame_fcm.jpg" draggable="true">
<img src="http://www.earthtimes.org/newsimage/lizard_Ngo_Van_Tri_big_281.jpg" draggable="true">
</div>
<div id="imagesCanvas"></div>
source to share