This post explains the basics of drag and drop.
Create draggable content
In most browsers, text selections, images, and links are draggable by default. For example, if you drag a link on a web page you'ill see a small box with a title and URL that you can drop on the address bar or the desktop to create a shortcut or 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, including images, files, links, files, or any
markup on your page.
The following 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
the draggable
attribute for each column 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. The only CSS related to the
drag feature is the cursor: move
property. The rest of the code 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 can drag the items, but nothing else happens. To add behavior, you need to use the JavaScript API.
Listen for dragging events
To monitor the drag process, you can listen for any of the following events:
To handle the drag flow, you need some kind of source element (where the drag starts), the data payload (the thing being dragged), and a target (an area to catch the drop). The source element can be almost any kind of element. The target is the drop zone or set of drop zones that accepts the data the user is trying to drop. Not all elements can be targets. For example, your target can't be an image.
Start and end a drag sequence
After you define draggable="true"
attributes on your content, attach a
dragstart
event handler to start the drag sequence for each column.
This code sets the column's opacity to 40% when the user starts 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 following Glitch demo. Drag an item, and its
opacity changes. Because the source element has the dragstart
event, setting
this.style.opacity
to 40% gives the user visual feedback that that element is
the current selection being moved. When you drop the item, the source element
returns to 100% opacity, even though you haven't defined the drop behavior yet.
Add additional visual cues
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 when they hold a dragged item over a
column. For example, in your CSS, you might create an over
class for
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 the dragged element leaves. 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
dragover
event is to set thedataTransfer.dropEffect
property to"none"
. ThedropEffect
property is covered later on this page. For now, just know that it prevents thedrop
event from firing. To override this behavior, calle.preventDefault()
. Another good practice is to returnfalse
in that same handler.The
dragenter
event handler is used to toggle theover
class instead ofdragover
. If you usedragover
, the event fires repeatedly while the user holds the dragged item over a column, causing the CSS class to toggle repeatedly. This makes the browser do a lot of unnecessary rendering work, which can affect the user experience. We strongly recommend minimizing redraws, and if you need to usedragover
, consider throttling or debouncing your event listener.
Complete the drop
To process the 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. To do this, call e.stopPropagation()
.
function handleDrop(e) {
e.stopPropagation(); // stops the browser from redirecting.
return false;
}
Be sure to register the new handler alongside 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 doesn't drop to the new location. To
make that happen, use the DataTransfer
object.
The dataTransfer
property holds the data sent in a drag action. dataTransfer
is set in the dragstart
event and read or 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 let users 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 by setting the source column's
HTML to the HTML of the target column that you dropped the data on. This
includes checking that the user isn't 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 and control how each drop target responds to a
particular data type.
dataTransfer.effectAllowed
restricts what 'type of drag' the user can perform on the element. It's used in the drag-and-drop processing model to initialize thedropEffect
during thedragenter
anddragover
events. The property can have the following values:none
,copy
,copyLink
,copyMove
,link
,linkMove
,move
,all
, anduninitialized
.dataTransfer.dropEffect
controls the feedback that the user gets during thedragenter
anddragover
events. When the user holds their pointer over a target element, the browser's cursor indicates what type of operation is going to take place, such as a copy or a move. 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 set a drag icon.
File upload
This simple example uses a column as both the drag source and drag target. This might happen in a UI that asks the user to rearrange items. In some situations, the drag target and source might be different element types, as in an interface where the user needs to select one image as the main image for a product by dragging the selected image onto a target.
Drag and Drop is frequently used to let users 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 is 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.