Drag and drop with HTML5 and JavaScript
Learn how to move elements between separate lists with vanilla JS and HTML5 drag and drop API
HTML5 provides a way to grab and move elements around the DOM and drop to them a specific location. The fact that this can be easily done with vanilla JS, without using any third-party libraries, makes it even more cool.
So, let's dive in. 👇
I will be using Bootstrap 5 for styling and icons. Add the following lines of code inside the <head>
tag of your index.html:
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Drag & drop</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.9.1/font/bootstrap-icons.css">
Inside your <body>
tag add the following HTML:
<div class="container py-5">
<h2 class="my-3">Drag and drop between lists</h2>
<div class="row">
<div class="col-6">
<div id="firstList" class="parent">
<div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="tomato">
<div class="card-body d-flex justify-content-between">
<span>
tomato
</span>
<button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
<i class="bi bi-x"></i>
</button>
</div>
</div>
<div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="potato">
<div class="card-body d-flex justify-content-between">
<span>
potato
</span>
<button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
<i class="bi bi-x"></i>
</button>
</div>
</div>
<div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="cucumber">
<div class="card-body d-flex justify-content-between">
<span>
cucumber
</span>
<button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
<i class="bi bi-x"></i>
</button>
</div>
</div>
<div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="carrot">
<div class="card-body d-flex justify-content-between">
<span>
carrot
</span>
<button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
<i class="bi bi-x"></i>
</button>
</div>
</div>
<div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="onion">
<div class="card-body d-flex justify-content-between">
<span>
onion
</span>
<button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
<i class="bi bi-x"></i>
</button>
</div>
</div>
<div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="lettuce">
<div class="card-body d-flex justify-content-between">
<span>
lettuce
</span>
<button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
<i class="bi bi-x"></i>
</button>
</div>
</div>
<div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="corn">
<div class="card-body d-flex justify-content-between">
<span>
corn
</span>
<button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
<i class="bi bi-x"></i>
</button>
</div>
</div>
</div>
</div>
<div class="col-6">
<div id="secondList" class="parent">
<div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="apple">
<div class="card-body d-flex justify-content-between">
<span>
apple
</span>
<button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
<i class="bi bi-x"></i>
</button>
</div>
</div>
<div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="peach">
<div class="card-body d-flex justify-content-between">
<span>
peach
</span>
<button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
<i class="bi bi-x"></i>
</button>
</div>
</div>
<div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="banana">
<div class="card-body d-flex justify-content-between">
<span>
banana
</span>
<button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
<i class="bi bi-x"></i>
</button>
</div>
</div>
<div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="fig">
<div class="card-body d-flex justify-content-between">
<span>
fig
</span>
<button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
<i class="bi bi-x"></i>
</button>
</div>
</div>
<div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="raspberry">
<div class="card-body d-flex justify-content-between">
<span>
raspberry
</span>
<button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
<i class="bi bi-x"></i>
</button>
</div>
</div>
<div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="lemon">
<div class="card-body d-flex justify-content-between">
<span>
lemon
</span>
<button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
<i class="bi bi-x"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<hr>
</div>
As you can see, we are setting the attribute draggable=true
on some elements. This enables listening to drag and drop events.
Now let's include Bootstrap's JavaScript from CDN and our own JavaScript from app.js file. I am adding them at the bottom of my <body>
.
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="app.js"></script>
Also let's create a simple form that will be used for creating new elements. These elements will not be assigned to any of the lists. Rather, these are unassigned elements and they can be dragged to one of the lists.
Now we have to write our JS code.
const unassignedItemsElement = document.querySelector("#unassignedItemsElement");
const addNewItem = document.querySelector("#addNewItem");
const form = document.querySelector("#form");
const lists = document.querySelectorAll(".parent");
form.addEventListener("submit", (e) => {
e.preventDefault();
addUnassignedElement(addNewItem.value);
addNewItem.value = "";
});
function addUnassignedElement(element) {
const id = `${element}-${Math.floor(Math.random() * 100) + 1}`;
unassignedItemsElement.innerHTML += `
<div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="${id}">
<div class="card-body d-flex justify-content-between">
<span>
${element}
</span>
<button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
<i class="bi bi-x"></i>
</button>
</div>
</div>`;
loopListItems();
}
function loopListItems() {
lists.forEach((list) => {
const listItems = list.querySelectorAll(".draggable");
listItems.forEach((item) => {
item.addEventListener("dragstart", onDragStart, false);
item.addEventListener("dragover", onDragOver, false);
item.addEventListener("drop", onDrop, false);
item.querySelector(".remove-btn").addEventListener("click", removeItem);
});
});
}
loopListItems();
We are handing form submit event and we are adding a new item to the unassigned list based on the form input. Now we have to handle drag and drop events that are added in loopListItem()
function. As you can see there are three events:
dragstart
,dragover
,drop
There are other events, but I am not using them since they are not important for our implementation right now. These are: dragenter
, dragleave
, dragend
, drag
.
let dragEl = null;
function onDragStart(event) {
dragEl = this;
event.dataTransfer.effectAllowed = "move";
event.dataTransfer.setData("node", this.id);
return false;
}
function onDragOver(event) {
if (event.preventDefault()) {
event.preventDefault();
}
event.dataTransfer.dropEffect = "move";
return false;
}
function onDrop(event) {
if (event.stopPropagation()) {
event.stopPropagation();
}
if (dragEl !== this) {
dragEl = this;
if (this.parentNode !== unassignedItemsElement) {
this.parentNode.insertBefore(document.getElementById(event.dataTransfer.getData("node")), this.nextSibling);
}
}
dragEl = null;
return false;
}
function removeItem(event) {
this.parentNode.parentNode.remove();
}
The names of these events are pretty self-expanatory, so I named the functions in similar fashion. onDragStart
is fired when the user starts dragging the element, onDragOver
is fired when the dragged element is over the drop target and onDrop
occurs when the dragged element is dropped on the drop target.
Inside these functions the this
keyword holds the value of the context of the current event that is being handled. We are assigning its value to dragEl
variable inside onDragStart
and inside onDrop
we are making sure that the element that is being currently dragged is actually different from the drop target element.
Another important thing to understand is dataTransfer
object and its properties:
As the element is being dragged, dataTransfer
is basically carrying the necessary information about the element and the operation that is being executed.
I recommend reading more about the HTML5 Drag and Drop API because the docs provide more detailed info.
If you want to get the code that I used as an example, head over to this GitHub repo, or you can see it in action here.