There's a bunch of libraries to facilitate drag-and-drop functionality on the web, but the native functionality built into modern browsers (and actually really well-supported now) is pretty cool and quite simple to use.
The basics
To start making something draggable, simply add draggable="true" to the element:
<div draggable="true" class="drag-me">
Drag me
</div>
<style>
.drag-me {
width: 5rem;
height: 5rem;
border-radius: 1rem;
background-color: forestgreen;
color: white;
display: flex;
justify-content: center;
align-items: center;
cursor: grab;
}
</style>
You will probably want to assign some data to this. To do this, we listen to the dragstart event, and use the event.dataTransfer.setData() method to set some data. You can even set multiple types of data by using various different mime types for the first parameter of that method.
Now we want to allow you to drop somewhere. By listening to the drop event on the target element, we can read out the data using event.dataTransfer.getData().
<div draggable="true" class="drag-me">
Drag me
</div>
<div class="drop-here">
Drop here
</div>
<script>
const mimeType = "application/x-my-app-data";
const source = document.querySelector(".drag-me");
source.addEventListener("dragstart", (event) => {
event.dataTransfer.setData(mimeType, "hello world");
});
const target = document.querySelector(".drop-here");
target.addEventListener("dragover", (event) => {
event.preventDefault();
});
target.addEventListener("drop", (event) => {
event.preventDefault();
const data = event.dataTransfer?.getData(mimeType);
event.target.textContent = data;
});
</script>
<style>
.drag-me, .drop-here {
width: 5rem;
height: 5rem;
border-radius: 1rem;
display: inline-flex;
justify-content: center;
align-items: center;
margin-right: 2rem;
}
.drag-me {
width: 5rem;
background-color: forestgreen;
cursor: grab;
color: white;
}
.drop-here {
width: 10rem;
border: 2px dashed;
}
</style>
We can easily make this more user-friendly by adding some styling. We can listen to the dragenter and dragleave events to add a CSS class, showing the user they can drop here:
<div draggable="true" class="drag-me">
Drag me
</div>
<div class="drop-here">
Drop here
</div>
<script>
const mimeType = "application/x-my-app-data";
const source = document.querySelector(".drag-me");
source.addEventListener("dragstart", (event) => {
event.dataTransfer.setData(mimeType, "hello world");
});
const target = document.querySelector(".drop-here");
target.addEventListener("dragover", (event) => {
event.preventDefault();
});
target.addEventListener("dragenter", (event) => {
event.target.classList.add("dragover");
});
target.addEventListener("dragleave", (event) => {
event.target.classList.remove("dragover");
});
target.addEventListener("drop", (event) => {
event.preventDefault();
const data = event.dataTransfer?.getData(mimeType);
event.target.textContent = data;
event.target.classList.add("dropped");
});
</script>
<style>
.drag-me, .drop-here {
width: 5rem;
height: 5rem;
border-radius: 1rem;
display: inline-flex;
justify-content: center;
align-items: center;
margin-right: 2rem;
}
.drag-me {
width: 5rem;
background-color: forestgreen;
cursor: grab;
color: white;
}
.drop-here {
width: 10rem;
border: 2px dashed;
}
.drop-here.dragover {
border-style: solid;
color: pink;
}
.drop-here.dropped {
background-color: pink;
border-style: sold;
border-color: pink;
color: black;
}
</style>
A custom drag image
If we go back to our simple example, there's another cool thing I want to show: whilst dragging, by default you'll be dragging something that looks exactly like the original element looked like.
Sometimes, however, you might want to show a different preview of the dragging item. You can do so by setting a custom dragImage:
<div draggable="true" class="drag-me">
Drag me
</div>
<script>
let preview;
const source = document.querySelector(".drag-me");
source.addEventListener("dragstart", (event) => {
preview = document.createElement("div");
preview.className = "drag-preview";
preview.innerHTML = "Drop me";
document.body.appendChild(preview);
event.dataTransfer.setDragImage(preview, 0, 0);
});
source.addEventListener("dragend", (event) => {
document.body.removeChild(preview);
});
</script>
<style>
.drag-me {
width: 5rem;
height: 5rem;
border-radius: 1rem;
background-color: forestgreen;
color: white;
display: flex;
justify-content: center;
align-items: center;
cursor: grab;
}
.drag-preview {
width: 7rem;
height: 3rem;
border-radius: 1rem;
background-color: navy;
color: white;
display: flex;
justify-content: center;
align-items: center;
position: fixed;
top: -10rem;
left: 0;
}
</style>
Downloading a file
Sometimes, you might want to drag something out of your browser and into your filesystem. The default action here might be to save a shortcut if the element you're dragging is an <a href>. Google Chrome has a cool trick up its sleeve: it allows you to specify a URL which will be downloaded as a file when dragged into the macOs Finder or Windows Explorer.
If you're using Chrome, try dragging this onto your desktop:
<div draggable="true" class="drag-me">
🐈 Drag me to the Finder
</div>
<script>
const source = document.querySelector(".drag-me");
source.addEventListener("dragstart", (event) => {
event.dataTransfer.setData(
"DownloadURL",
"image/png:cat.png:https://schof.co/cat.png",
);
});
</script>
<style>
.drag-me {
width: 14rem;
height: 4rem;
border-radius: 1rem;
background-color: skyblue;
color: black;
display: flex;
justify-content: center;
align-items: center;
cursor: grab;
}
</style>