Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Snapping Drag and Drop Items in JavaScript

I am trying to use drag and drop to move pictures from one <div> to another.

Currently, I can move the pictures anywhere in the destination <div>, but what I really want is that the pictures snap together when dropped. Ideally, they would be able to snap together on any side (not just, for example, on the bottom or on the right).

I've tried a few different things (including using <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>
like image 751
Yikes Avatar asked Oct 18 '22 10:10

Yikes


1 Answers

When you start dragging an image, you need to store the position of the cursor, relative to that particular image.

There are multiple position properties in the MouseEvent that will help you calculate that, but if browser support is not an issue, I would go for MouseEvent.offsetX and MouseEvent.offsetY. From the docs:

The offsetX/offsetY read-only property of the MouseEvent interface provides the offset in the X/Y coordinate of the mouse pointer between that event and the padding edge of the target node.

So, on dragstart, you will do just:

x = e.offsetX;
y = e.offsetY;

Then, when you drop the image in your, let's call it canvas (note the italics as it is not a <canvas> element, but any other element that you use as your drop zone, a <div> in this particular example), you need to know the position of the cursor relative to that canvas, so you may think you could use offsetX and offsetY again, and your are partially right. That would give you the expected value if you drop the image on the canvas itself, but there may be other images in it already, and you may drop the current one on top of another one, getting the offsetX and offsetY relative to that one instead.

What you can do is use MouseEvent.pageX and MouseEvent.pageY and, from that value, subtract the position of the (upper-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 will get the position of the cursor relative to the canvas element.

Now, you need to subtract the x and y values that you stored on dragstart, and that will give you the left and top values of the upper-left corner of the dragged image, relative to the canvas element:

image.style.left = (e.pageX - imagesCanvas.offsetLeft - x) + 'px';
image.style.top = (e.pageY - imagesCanvas.offsetTop - y) + 'px';

All 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>
like image 105
Danziger Avatar answered Oct 20 '22 22:10

Danziger