Using the HTML5 Drag and Drop API
The HTML5 Drag and Drop API means that we can make almost any element on our page draggable. In this post we'll explain the basics of drag and drop.
Creating draggable content #
It's worth noting that in most browsers, text selections, images, and links are draggable by default. For example, if you drag a link on a web page you will see a small box with a title and URL. You can drop this box on the address bar or the desktop to create a shortcut or to navigate to the link. To make other types of content draggable you need to use the HTML5 Drag and Drop APIs.
To make an object draggable set draggable=true on that element. Just about anything can be
drag-enabled: images, files, links, files, or any markup on your page.
Our example creates an interface to rearrange columns that have been laid out with
CSS Grid. The basic markup for the columns looks like this, with each column having the draggable
attribute set to true.
<div class="container">
<div draggable="true" class="box">A</div>
<div draggable="true" class="box">B</div>
<div draggable="true" class="box">C</div>
</div>
Here's the CSS for the container and box elements. Note that the only CSS related to drag and drop
functionality is the cursor: move property.
The rest of the code just controls the layout and styling of the container and box elements.
.container {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 10px;
}
.box {
border: 3px solid #666;
background-color: #ddd;
border-radius: .5em;
padding: 10px;
cursor: move;
}
At this point you will find that you can drag the items, however nothing else will happen. To add the drag and drop functionality we need to use the JavaScript API.
Listening for dragging events #
There are a number of different events to attach to for monitoring the entire drag and drop process.
To handle the drag and drop flow, you need some kind of source element (where the drag originates), the data payload (what you're trying to drop), and a target (an area to catch the drop). The source element can be an image, list, link, file object, block of HTML, etc. The target is the drop zone (or set of drop zones) that accepts the data the user is trying to drop. Keep in mind that not all elements can be targets, for example an image can't be a target.
Starting and ending a drag and drop sequence #
Once you have draggable="true" attributes defined on your content, attach a dragstart event
handler to kick off the drag and drop sequence for each column.
This code will set the column's opacity to 40% when the user begins dragging it, then return it to 100% when the dragging event ends.
function handleDragStart(e) {
this.style.opacity = '0.4';
}
function handleDragEnd(e) {
this.style.opacity = '1';
}
let items = document.querySelectorAll('.container .box');
items.forEach(function (item) {
item.addEventListener('dragstart', handleDragStart);
item.addEventListener('dragend', handleDragEnd);
});
The result can be seen in the Glitch demo below. Drag an item and it becomes opaque. As the
dragstart event's target is the source element, setting this.style.opacity to 40% gives the user
visual feedback that the element is the current selection being moved. Once you drop the item,
although the drop functionality is not in place, the source element returns to 100% opacity.
Add additional visual cues with dragenter, dragover, and dragleave #
To help the user understand how to interact with your interface, use the dragenter, dragover and
dragleave event handlers. In this example the columns are drop targets in addition to being
draggable. Help the user to understand this by making the border dashed as they hold a dragged item
over a column. For example, in your CSS you might create an over class to represent elements that
are drop targets:
.box.over {
border: 3px dotted #666;
}
Then, in your JavaScript set up the event handlers, add the over class when the column is dragged
over, and remove it when we leave. In the dragend handler we also make sure to remove the classes
at the end of the drag.
document.addEventListener('DOMContentLoaded', (event) => {
function handleDragStart(e) {
this.style.opacity = '0.4';
}
function handleDragEnd(e) {
this.style.opacity = '1';
items.forEach(function (item) {
item.classList.remove('over');
});
}
function handleDragOver(e) {
e.preventDefault();
return false;
}
function handleDragEnter(e) {
this.classList.add('over');
}
function handleDragLeave(e) {
this.classList.remove('over');
}
let items = document.querySelectorAll('.container .box');
items.forEach(function(item) {
item.addEventListener('dragstart', handleDragStart);
item.addEventListener('dragover', handleDragOver);
item.addEventListener('dragenter', handleDragEnter);
item.addEventListener('dragleave', handleDragLeave);
item.addEventListener('dragend', handleDragEnd);
item.addEventListener('drop', handleDrop);
});
});
There are a couple of points worth covering in this code:
-
The default action for
dragoverevent is to set thedataTransfer.dropEffectproperty to"none". ThedropEffectproperty is covered later in this article. For now, just know that it prevents thedropevent from being fired. To override this behavior, calle.preventDefault(). Another good practice is to returnfalsein that same handler. -
The
dragenterevent handler is used to toggle theoverclass instead ofdragover. If you usedragover, the CSS class would be toggled many times as the eventdragovercontinued to fire on a column hover. Ultimately, that would cause the browser's renderer to do a large amount of unnecessary work. Keeping redraws to a minimum is always a good idea. If you need to use thedragoverevent for something, consider throttling or debouncing your event listener.
Completing the drop #
To process the actual drop, add an event listener for the drop event. In the drop handler,
you'll need to prevent the browser's default behavior for drops, which is typically some sort of
annoying redirect. You can prevent the event from bubbling up the DOM by calling
e.stopPropagation().
function handleDrop(e) {
e.stopPropagation(); // stops the browser from redirecting.
return false;
}
Be sure to register the new handler in amongst the other handlers:
let items = document.querySelectorAll('.container .box');
items.forEach(function(item) {
item.addEventListener('dragstart', handleDragStart);
item.addEventListener('dragover', handleDragOver);
item.addEventListener('dragenter', handleDragEnter);
item.addEventListener('dragleave', handleDragLeave);
item.addEventListener('dragend', handleDragEnd);
item.addEventListener('drop', handleDrop);
});
If you run the code at this point, the item will not drop to the new location. To achieve this you
need to use the DataTransfer object.
The dataTransfer property is where all the drag and drop magic happens. It holds the piece of data sent in a
drag action. dataTransfer is set in the dragstart event and read/handled in the drop event.
Calling e.dataTransfer.setData(mimeType, dataPayload) lets you set the object's MIME type and data
payload.
In this example, we're going to allow users to rearrange the order of the columns. To do that, first you need to store the source element's HTML when the drag starts:
function handleDragStart(e) {
this.style.opacity = '0.4';
dragSrcEl = this;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.innerHTML);
}
In the drop event you process the column drop, setting the source column's HTML to the HTML of the
target column that you dropped on, first checking that the user is not dropping back onto the same
column they dragged from.
function handleDrop(e) {
e.stopPropagation();
if (dragSrcEl !== this) {
dragSrcEl.innerHTML = this.innerHTML;
this.innerHTML = e.dataTransfer.getData('text/html');
}
return false;
}
You can see the result in the following demo. For this to work, you'll need a desktop browser. The Drag and Drop API isn't supported on mobile. Drag and release the A column on top of the B column and notice how they change places:
More dragging properties #
The dataTransfer object exposes properties to provide visual feedback to the user during the drag
process. These properties can also be used to control how each drop target responds to a particular
data type.
dataTransfer.effectAllowedrestricts what 'type of drag' the user can perform on the element. It is used in the drag-and-drop processing model to initialize thedropEffectduring thedragenteranddragoverevents. The property can be set to the following values:none,copy,copyLink,copyMove,link,linkMove,move,all, anduninitialized.dataTransfer.dropEffectcontrols the feedback that the user is given during thedragenteranddragoverevents. When the user hovers over a target element, the browser's cursor indicates what type of operation is going to take place (e.g. a copy, a move, etc.). The effect can take one of the following values:none,copy,link,move.e.dataTransfer.setDragImage(imgElement, x, y)means that instead of using the browser's default 'ghost image' feedback, you can optionally set a drag icon.
File upload with drag and drop #
This simple example uses a column as both the drag source and drag target. This might be seen in a UI where the user is asked to rearrange the items. In some situations the drag target and source may be different, such as an interface where the user needs to select one image to be the main image for a product by dragging the selected image onto a target.
Drag and Drop is frequently used to allow users to drag items from their desktop into an
application. The main difference is in your drop handler. Instead of using
dataTransfer.getData() to access the files, their data will be contained in the
dataTransfer.files property:
function handleDrop(e) {
e.stopPropagation(); // Stops some browsers from redirecting.
e.preventDefault();
var files = e.dataTransfer.files;
for (var i = 0, f; (f = files[i]); i++) {
// Read the File objects in this FileList.
}
}
You can find more information about this in Custom drag-and-drop.
More resources #
- The Drag and Drop Specification
- MDN HTML Drag and Drop API
- How To Make A Drag-and-Drop File Uploader With Vanilla JavaScript
- Creating a Parking Game With the HTML Drag and Drop API
- How To Use The HTML Drag-and-Drop API In React