frontend
Drag and Drop API in JavaScript
January 24, 2026
Drag and Drop API in JavaScript
Overview
The HTML5 Drag and Drop API allows users to drag elements and drop them into target areas. This API provides a native way to implement drag-and-drop functionality without external libraries, making it perfect for file uploads, reordering lists, and interactive interfaces.
Basic Implementation
function drag(ev) {
ev.dataTransfer.setData("text/plain", ev.target.outerHTML);
}
function allowDrop(ev) {
ev.preventDefault();
}
function drop(ev) {
ev.preventDefault();
// Retrieve the HTML content from the dataTransfer object
const data = ev.dataTransfer.getData("text/plain");
// Create a new div with the dragged content
const draggedElement = document.createElement("div");
draggedElement.innerHTML = data;
// Append the cloned element to the drop target
ev.target.appendChild(draggedElement.firstChild);
// Remove the original dragged element
ev.target.previousElementSibling.remove();
}
HTML Setup
<!-- Draggable element -->
<div draggable="true" ondragstart="drag(event)">
Drag me
</div>
<!-- Drop target -->
<div ondrop="drop(event)" ondragover="allowDrop(event)">
Drop here
</div>
Drag Events
On Draggable Element
- ondragstart: Fires when dragging starts
- ondrag: Fires continuously while dragging
- ondragend: Fires when dragging ends
On Drop Target
- ondragenter: Fires when dragged element enters drop target
- ondragover: Fires continuously while over drop target
- ondragleave: Fires when dragged element leaves drop target
- ondrop: Fires when element is dropped
DataTransfer Object
The dataTransfer object stores data during drag operations.
Setting Data
function dragStart(e) {
// Set text data
e.dataTransfer.setData("text/plain", "Hello World");
// Set custom data
e.dataTransfer.setData("application/json", JSON.stringify({ id: 123 }));
// Set HTML
e.dataTransfer.setData("text/html", e.target.outerHTML);
}
Getting Data
function drop(e) {
e.preventDefault();
// Get text data
const text = e.dataTransfer.getData("text/plain");
// Get custom data
const json = JSON.parse(e.dataTransfer.getData("application/json"));
// Get HTML
const html = e.dataTransfer.getData("text/html");
}
Complete Example
class DragAndDrop {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.setupDragAndDrop();
}
setupDragAndDrop() {
// Make all children draggable
Array.from(this.container.children).forEach(item => {
item.draggable = true;
item.addEventListener("dragstart", (e) => this.handleDragStart(e));
item.addEventListener("dragend", (e) => this.handleDragEnd(e));
});
// Setup drop zones
this.container.addEventListener("dragover", (e) => this.handleDragOver(e));
this.container.addEventListener("drop", (e) => this.handleDrop(e));
}
handleDragStart(e) {
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("text/html", e.target.outerHTML);
e.dataTransfer.setData("text/plain", e.target.id);
e.target.classList.add("dragging");
}
handleDragEnd(e) {
e.target.classList.remove("dragging");
}
handleDragOver(e) {
if (e.preventDefault) {
e.preventDefault();
}
e.dataTransfer.dropEffect = "move";
return false;
}
handleDrop(e) {
if (e.stopPropagation) {
e.stopPropagation();
}
const draggedId = e.dataTransfer.getData("text/plain");
const draggedElement = document.getElementById(draggedId);
const dropTarget = e.target.closest(".drop-target") || this.container;
if (draggedElement && dropTarget) {
dropTarget.appendChild(draggedElement);
}
return false;
}
}
File Drag and Drop
function setupFileDrop(dropZone) {
dropZone.addEventListener("dragover", (e) => {
e.preventDefault();
e.stopPropagation();
dropZone.classList.add("drag-over");
});
dropZone.addEventListener("dragleave", (e) => {
e.preventDefault();
e.stopPropagation();
dropZone.classList.remove("drag-over");
});
dropZone.addEventListener("drop", (e) => {
e.preventDefault();
e.stopPropagation();
dropZone.classList.remove("drag-over");
const files = e.dataTransfer.files;
handleFiles(files);
});
}
function handleFiles(files) {
Array.from(files).forEach(file => {
if (file.type.startsWith("image/")) {
const reader = new FileReader();
reader.onload = (e) => {
displayImage(e.target.result);
};
reader.readAsDataURL(file);
}
});
}
Visual Feedback
function setupVisualFeedback() {
const draggable = document.querySelector(".draggable");
const dropZone = document.querySelector(".drop-zone");
draggable.addEventListener("dragstart", () => {
draggable.style.opacity = "0.5";
});
draggable.addEventListener("dragend", () => {
draggable.style.opacity = "1";
});
dropZone.addEventListener("dragenter", () => {
dropZone.classList.add("drag-over");
});
dropZone.addEventListener("dragleave", () => {
dropZone.classList.remove("drag-over");
});
}
Advanced: Sortable List
class SortableList {
constructor(listId) {
this.list = document.getElementById(listId);
this.setupSortable();
}
setupSortable() {
Array.from(this.list.children).forEach((item, index) => {
item.draggable = true;
item.dataset.index = index;
item.addEventListener("dragstart", (e) => this.onDragStart(e));
});
this.list.addEventListener("dragover", (e) => this.onDragOver(e));
this.list.addEventListener("drop", (e) => this.onDrop(e));
}
onDragStart(e) {
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("text/html", e.target.outerHTML);
e.dataTransfer.setData("text/plain", e.target.dataset.index);
e.target.style.opacity = "0.4";
}
onDragOver(e) {
if (e.preventDefault) {
e.preventDefault();
}
e.dataTransfer.dropEffect = "move";
const afterElement = this.getDragAfterElement(this.list, e.clientY);
const dragging = document.querySelector(".dragging");
if (afterElement == null) {
this.list.appendChild(dragging);
} else {
this.list.insertBefore(dragging, afterElement);
}
}
getDragAfterElement(container, y) {
const draggableElements = [...container.querySelectorAll(".item: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;
}
onDrop(e) {
if (e.stopPropagation) {
e.stopPropagation();
}
const draggedIndex = parseInt(e.dataTransfer.getData("text/plain"));
const items = Array.from(this.list.children);
const newIndex = items.indexOf(e.target);
if (draggedIndex !== newIndex) {
const draggedItem = items[draggedIndex];
this.list.removeChild(draggedItem);
this.list.insertBefore(draggedItem, items[newIndex]);
}
return false;
}
}
DataTransfer Properties
function handleDragStart(e) {
// Set drag effect
e.dataTransfer.effectAllowed = "copy"; // or "move", "link", "copyMove", etc.
// Set drop effect
e.dataTransfer.dropEffect = "move";
// Set custom drag image
const dragImage = document.createElement("div");
dragImage.textContent = "Custom drag image";
e.dataTransfer.setDragImage(dragImage, 0, 0);
// Set data
e.dataTransfer.setData("text/plain", "data");
// Files (read-only)
const files = e.dataTransfer.files;
}
Use Cases
1. File Upload
const dropZone = document.getElementById("drop-zone");
dropZone.addEventListener("drop", (e) => {
e.preventDefault();
const files = Array.from(e.dataTransfer.files);
uploadFiles(files);
});
2. Kanban Board
class KanbanBoard {
moveCard(cardId, fromColumn, toColumn) {
const card = document.getElementById(cardId);
toColumn.appendChild(card);
this.updateCardPosition(cardId, toColumn.id);
}
}
3. Shopping Cart
function addToCart(productId) {
const product = document.getElementById(productId);
const cart = document.getElementById("cart");
cart.appendChild(product.cloneNode(true));
}
Best Practices
- Always preventDefault: On dragover and drop events
- Provide Visual Feedback: Show drag state clearly
- Handle Edge Cases: Check for valid drop targets
- Clean Up: Remove event listeners when done
- Accessibility: Provide keyboard alternatives
- Mobile Support: Consider touch events for mobile
Browser Compatibility
Drag and Drop API is supported in all modern browsers. For older browsers, consider using libraries like SortableJS or interact.js.
Common Issues
Issue 1: Drop not working
// Solution: Always preventDefault on dragover
element.addEventListener("dragover", (e) => {
e.preventDefault(); // Required!
});
Issue 2: Drag image not showing
// Solution: Set drag image before setting data
e.dataTransfer.setDragImage(customImage, 0, 0);
e.dataTransfer.setData("text/plain", "data");
Issue 3: Data not transferring
// Solution: Use same data type for get and set
e.dataTransfer.setData("text/plain", data);
const retrieved = e.dataTransfer.getData("text/plain");