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 atitle and URL that you can drop on the address bar or the desktop to create ashortcut or navigate to the link. To make other types of content draggable, youneed to use the HTML5 Drag and Drop APIs.
To make an object draggable, set draggable=true
on that element. Just aboutanything can be drag-enabled, including images, files, links, files, or anymarkup on your page.
The following example creates an interface to rearrange columns that have beenlaid 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 thedrag feature is the cursor: move
property. The rest of the code controls the layout and styling of the containerand 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 addbehavior, 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 dragstarts), the data payload (the thing being dragged), and a target (an area tocatch the drop). The source element can be almost any kind of element. Thetarget is the drop zone or set of drop zones that accepts the data the user istrying to drop. Not all elements can be targets. For example, your target can'tbe an image.
Start and end a drag sequence
After you define draggable="true"
attributes on your content, attach adragstart
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 itsopacity changes. Because the source element has the dragstart
event, settingthis.style.opacity
to 40% gives the user visual feedback that that element isthe current selection being moved. When you drop the item, the source elementreturns 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 thedragenter
, dragover
and dragleave
event handlers. In this example, thecolumns are drop targets in addition to being draggable. Help the user tounderstand this by making the border dashed when they hold a dragged item over acolumn. For example, in your CSS, you might create an over
class forelements that are drop targets:
.box.over { border: 3px dotted #666;}
Then, in your JavaScript, set up the event handlers, add the over
class whenthe column is dragged over, and remove it when the dragged element leaves. Inthe dragend
handler we also make sure to remove the classes at the end of thedrag.
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 actionfor
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 thisbehavior, 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 userholds the dragged item over a column, causing the CSS class to togglerepeatedly. This makes the browser do a lot of unnecessary rendering work,which can affect the user experience. We strongly recommend minimizingredraws, and if you need to usedragover
, considerthrottling 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, whichis 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. Tomake 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. Callinge.dataTransfer.setData(mimeType, dataPayload)
lets you set the object's MIMEtype 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 dragstarts:
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'sHTML to the HTML of the target column that you dropped the data on. Thisincludes checking that the user isn't dropping back onto the same column theydragged 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 adesktop browser. The Drag and Drop API isn't supported on mobile. Drag andrelease 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 theuser during the drag process and control how each drop target responds to aparticular data type.
dataTransfer.effectAllowed
restricts what 'type of drag' the user can perform on the element. It's usedin the drag-and-drop processing model to initialize thedropEffect
duringthedragenter
anddragover
events. The property can have thefollowing 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'scursor indicates what type of operation is going to take place, such as a copyor 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, youcan set a drag icon.
File upload
This simple example uses a column as both the drag source and drag target. Thismight 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 interfacewhere the user needs to select one image as the main image for a product bydragging the selected image onto a target.
Drag and Drop is frequently used to let users drag items from their desktop intoan application. The main difference is in your drop
handler. Instead of usingdataTransfer.getData()
to access the files, their data is contained in thedataTransfer.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 inCustom drag-and-drop.