'How to make drag and drop using vanilla javascript?
I have written a code for a drag and drop using vanilla javascript. When I pick the first div, it does move to last automatically while I was trying to move next.
I don't want to use the jquery library.
What I tried is I created the main div that acts as a container so that inside of that container each div can be moved easily
I have attached what I tried, but I don't understand why this is happening.
const draggables = document.querySelectorAll('.draggable')
const containers = document.querySelectorAll('.mainContainer')
draggables.forEach(draggable => {
draggable.addEventListener('dragstart', () => {
draggable.classList.add('dragging')
})
draggable.addEventListener('dragend', () => {
draggable.classList.remove('dragging')
})
})
containers.forEach(container => {
container.addEventListener('dragover', e => {
e.preventDefault()
const afterElement = getDragAfterElement(container, e.clientY)
const draggable = document.querySelector('.dragging')
if (afterElement == null) {
container.appendChild(draggable)
} else {
container.insertBefore(draggable, afterElement)
}
})
})
function getDragAfterElement(container, y) {
const draggableElements = [...container.querySelectorAll('.draggable:not(.dragging)')]
return draggableElements.reduce((closest, child) => {
const box = child.getBoundingClientRect()
const offset = y - box.top - box.height / 2
if (offset < 0 && offset > closest.offset) {
return { offset: offset, element: child }
} else {
return closest
}
}, { offset: Number.NEGATIVE_INFINITY }).element
}
body {
font-family: sans-serif;
}
.mainContainer {
padding: 6px 0;
width: 100%;
}
.draggable {
cursor: move;
display: inline-block;
margin: 0 6px;
z-index: 1;
color: #fff;
background-color: #087ee5;
border: 1px solid #0675d6;
border-radius: 4px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
text-align: center;
max-width: 300px;
min-width: 30px;
border-collapse: separate;
vertical-align: middle;
padding-left: 7px;
padding-right: 7px;
padding-top: 10px;
padding-bottom: 10px;
}
.draggable.dragging {
opacity: 0.5;
}
<div class="mainContainer">
<p class="draggable" draggable="true">MOVE 1</p>
<p class="draggable" draggable="true">MOVE 2</p>
<p class="draggable" draggable="true">MOVE 3</p>
<p class="draggable" draggable="true">MOVE 4</p>
</div>
Solution 1:[1]
Add an event listener for the dragables on mouseover. check if the dragable is already in your hand. if not than append the Node of the dragable in your hand after the Node with mouseover
change the position of the dragged element to absolute and define its position x & y through top and left
Solution 2:[2]
I would recommend NOT using the drag events and instead use mouse events. I have had MUCH better luck and reliability with this and you get off screen dragging as well:
handleDragStart = () => {
// Once mouse down is detected, you THEN start up the window listeners
// You also append something to the body of your DOM to escape
// your drag graphic from getting covered
window.addEventListener('mousemove', handleDragItem);
// Use mouse up to see if you moused up on something you were targetting
// You can use mouse over events on your targets to have easy state tracking
// No matter what, when window mouse up happens, you clean up all
// event listeners involved
window.addEventListener('mouseup', handleStopDrag);
});
Each element involved int he drag and drop can have these events as well paired with the starter window event (a JSX way to look at it to save some typing)
<div
onMouseDown={this.handleDragStart(index, width, height)}
onMouseMove={this.handleDragEnter(index)}
onMouseUp={this.handleDrop(index)}
onMouseOut={this.handleDragOut(index)}
/>
Solution 3:[3]
If your issue here is to stop dragging and place the object you'r dragging into, I would define "areas" where you could drop your element, this areas depend on how many elements you have, in this case (4) you could have the same areas
1 area will be the same are that the dragging element has 3 areas are in between the other elements + the start and end space
so you need to calculate when this element is going to be dropped, I also would use the events mousedown and mouseup and if you want to know if the element is being "dragged" you can check if the element was mousedown + mousemove
In my case I did a simliar solution before and I made this "areas" making half of each element and that way it could have some space between those, and the far left and right could be < first_element.position.X and > last_element.position.X
Solution 4:[4]
If your list is limited to one row adding calculation for X-axis can fix the problem.
Simply change e.clientY to e.clientX
const afterElement = getDragAfterElement(container, e.clientX)
Use left coordinates and the width rather than height and top
offset = x - box.left - box.width / 2;
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Momo |
| Solution 2 | Diniden |
| Solution 3 | roier.rdz |
| Solution 4 | EmPlusPlus |
