JavaScript Drag-and-Drop Basics
Learning outcomes:
- What are draggable items
- What are dropzones
- Default drag-and-drop behavior in browsers
- The
draggable
HTML attribute - The
dragstart
,dragover
anddrop
events - A complex drag-and-drop example
The DnD API
Back in the day, when Internet Explorer 5 was about to be shipped and HTML5 was already a recent technology, IE launched a new API to enable native drag-and-drop functionality. The developer could use this API to customize things as they were dragged and dropped.
With newer and newer versions of the browser, Microsoft kept on adding more and more bulk to this API ultimately leading to the API finding itself all the way in the HTML5 specification and hence other browsers implementing it.
Strictly speaking, the implementation of IE for its drag-and-drop functionality was reverse-engineered by Ian Hickson while writing its spec for the HTML5 specification. This specification was then followed by other web browsers, such as Google Chrome, Firefox, Safari, Opera, etc. (although not very strictly).
In these early days, the implementation of the DnD (drag-and-drop) API was extremely inconsistent and buggy across various browsers. Some features worked in some browsers; some didn't. However, today, at the time of this writing, all major web browsers have excellent support for basic DnD functionality.
Even though this is some, arguably irrelevant, history, it does tell us about one of the many contributions of IE to web technologies — well, IE has quite many of them!
With this brief history out of the way, let's now begin the real discussion.
Draggable items
We'll start off by understanding what exactly is meant by being 'draggable'.
By default, only the following items are draggable on a webpage:
- Links (given by
<a>
with anhref
attribute) - Images (given by
<img>
) - Text selections
Nothing else is draggable.
So uptil this point, we know that not everything is capable of being dragged in web browsers, to begin with. Only three things are: links, images and text selections.
draggable
attribute.Now once something is selected and thereby dragged, it eventually has to be put somewhere, or better to say, dropped somewhere.
This gives birth to another concept:
A dropzone is also referred to as a drop region, droppable region, droppable element, destination element, or simply a droppable.
Once again, not everything on a webpage is a dropzone by default.
Only the following are considered dropzones by default:
- Text input boxes (given by
<input type="text">
) - Textarea boxes (given by
<textarea>
) - Any element with
contenteditable
set to"true"
.
Nothing else!
So uptil this point, it's clear that a dropzone is an element that accepts drops, and that not everything is a dropzone by default — only the ones listed above enjoy this facility.
This means that we could drag an <img>
element (which is draggable by default) and drop it into a <textarea>
element (which is droppable by default).
The consequence of doing so is interesting...
When we drop an <img>
inside a text box, <textarea>
in this case, the fully-qualified src
of the image is put inside the text box.
You can check this yourself below:
Try dragging and then dropping the image into the text box. As soon as you do so, you'll notice the box filled up with the src of the image.
Now this was about images; what about other draggable stuff?
Let's define the consequence for all of them:
- When an
<a>
element is dropped onto any droppable element, itshref
attribute is copied onto the textbox. - When a text selection is dropped over a textbox, the selected text is put inside the box. If the selected text was from a textbox itself, then that text is removed from the original textbox, by default. However, by pressing the Ctrl key, we could change this and prevent the text from being removed.
All these can be seen in the live previews below:
To boil it all down, web browsers provide quite a lot of drag-and-drop functionality by default. But generally, all this is not very frequently used.
How many times do you remember dragging text from an input field into another field and dropping it there with the Ctrl key pressed? You, more than often, copy the text and paste it into the desired field, isn't that so?
How many times do you drag links into textboxes?
Or for that matter, how many times do you drag images into textboxes? Once in a month, once in a year? Or maybe never?
One thing known for sure is that these out-of-the-box DnD facilities provided by web browsers aren't that handy. OK, let's be fair — they aren't handy at all!
Tapping into this default mechanism is utterly necessary and the whole point of this unit, if our goal is to create useful DnD experiences for the end user.
In the discussion that follows, we'll see how to create customized DnD functionality.
The draggable
attribute
The first step to configure DnD functionality is the draggable
HTML attribute.
Let's see what it does...
As the name suggests,
draggable
HTML attribute makes an element draggable.This is the simplest of all definitions.
By default, as we stated earlier, not all elements on a webpage are draggable. However, by setting the draggable
attribute to "true"
, we can make any element draggable.
Shown below is an example:
<div>Drag and Drop</div>
div {
backgrond-color: #fff493; /* light tint of yellow */
padding: 50px;
display: inline-block
}
We have a <div>
element with some CSS styles applied to give it a large clickable area.
Try dragging this <div>
, by pressing the mouse button down while the pointer is over it and then moving the mouse while the button is held down. You'll most probably end up making a text selection — this is because we can't drag this element.
Let's go forward and add the draggable
attribute:
<div draggable="true">Drag and Drop</div>
Here's the new output:
Visually, the output is the same as before. But this time, as you drag the <div>
, you'll see the magic of the draggable
attribute.
Upon dragging the <div>
, we see a translucent, replica image of it moving along with the pointer. This confirms that the <div>
is draggable.
draggable
ain't a Boolean attribute!
The draggable
attribute is not a Boolean attribute in HTML, unlike many other attributes. That is, we couldn't set it as follows:
<div draggable>This is wrong</div>
It must have a value, as shown below:
<div draggable="true">This is draggable</div>
<div draggable="false">This is not draggable</div>
"true"
means that the element is draggable, while "false"
means that the element is not draggable.
So now if you're asked to reason about the purpose of the draggable
attribute, you'd have an answer right away: draggable
is required to make an element draggable.
Without draggable
, we couldn't manually configure the drag-and-drop behavior of an HTML element — we would never be able to drag it, likewise any sort of drag-and-drop configurations would go ignored (unless that element is draggable by default).
draggable="true"
on links or images is redundant since these elements are draggable by default. However, we could use the attribute to turn off this feature for these elements, i.e. by setting draggable="false"
.The dragstart
event
After setting the draggable
attribute on an element, the next step is to handle the dragstart
event on the element.
Let's see what dragstart
is.
dragstart
event.As the name suggests, dragstart
implies the 'start' of the drag action.
Subsequent code can obviously be then executed by handling this dragstart
event.
But what action could we possibly want to do upon dragstart
? Well, we could set some data for the respective drag operation.
For instance, if we drag a <div>
, we could save its reference in a global variable and then retrieve the value of this variable later on when we drop the <div>
in a dropzone. We'll see such an example later on in this chapter.
The question is: Why do we even need consider dragstart
? Can't we implement DnD functionality without it?
We could, but only for elements that are draggable by default. For other elements, we couldn't do anything useful without listening to the dragstart
event.
But why? Let's see...
Suppose we drag a <div>
element (which was obviously made draggable using the draggable
attribute), and then drop it to a dropzone. What would happen at this point? Yes, we haven't shown you how to work with dropzones, but let's imagine for the moment that the dropzone is fully configured. What would happen?
Well, if we did not listen to the dragstart
event, we wouldn't have specified the data associated with the current drag. This would've meant that the moment we drop the <div>
into the dropzone, our application would have no clue regarding what has been dropped — surprisingly, there's no way to retrieve the element that has been dropped over a dropzone.
In short, there would be absolutely NO data at all to work with!
All this simply means one thing: to set the data associated with a drag operation, we need to set it manually as soon as the drag operation begins, which is marked by the dragstart
event.
dragstart
where we could set data for the ongoing drag operation. We could do this inside the drag
event's handler as well, which we shall explore in the JavaScript Drag and Drop — Draggable Elements chapter.But sensibly speaking,
dragstart
represents the start of a drag event, and it makes much more sense to set the data at this point, rather than when the drag has already begun. Moreover, drag
tends to fire continuously, which would make the data be set again and again — something clearly unnecessary and inefficient!Let's now handle the dragstart
event for our draggable <div>
element created above:
<div draggable="true">Drag and Drop</div>
var draggedElement;
var divElement = document.querySelector('div');
divElement.ondragstart = function(e) {
draggedElement = e.target;
}
First we create a global variable draggedElement
to hold a reference to the dragged element later on. Next, we select the <div>
element in the variable divElement
and then set up a dragstart
handler on it. Inside the handler, we save the event's target
inside draggedElement
.
At least for now, you won't find this code snippet useful at all.
After all, we could've directly assigned divElement
to draggedElement
inside the handler, instead of going with e.target
, or simply just removed the dragstart
handler since we already have a reference to the <div>
element.
However, once we add more draggable <div>
s, which we would very soon in this chapter, you'd see the benefit of using e.target
inside the dragstart
handler.
Anyways, so the code above gets the draggedElement
variable filled up with the reference of <div>
as soon as it's dragged. We could confirm this using a very simple log:
var draggedElement;
var divElement = document.querySelector('div');
divElement.ondragstart = function(e) {
draggedElement = e.target;
console.log(draggedElement === divElement); // true
}
As soon as we drag the <div>
, true
is logged in the console. This means that yes, draggedElement
is exactly the same element as divElement
.
Perfect!
It's time to finally take this code snippet up a notch by creating and configuring a dropzone.
The dragover
event
As stated before, NOT every element on a webpage is considered a dropzone, by default.
Only the following elements are considered as dropzones:
- Text input boxes (given by
<input type="text">
) - Textarea boxes (given by
<textarea>
) - Any element with
contenteditable
set to"true"
.
Fortunately though, we can make any element a dropzone using the DnD API.
The main step in doing so is to handle the dragover
event on the respective element.
So what is dragover
?
dragover
event fires on an element as soon as a draggable item is dragged over it.The dragover
event fires repeatedly, after short regular intervals, even if we don't move the mouse pointer at all. The event only stops firing when the pointer leaves the element.
Perhaps, the most important thing to note here is that the browser nullifies the drag operation in its default dragover
handling mechanism (specifically, it sets dropEffect
to 'none'
; we'll see what this means). This prevents the subsequent drop
event from firing.
Likewise, in order to be able to process a subsequent drop (of a draggable element), it's utmost necessary to prevent this default behavior. And that, obviously, means calling the preventDefault()
method of the dragover
event.
returnValue
property to false
.To restate it: handling the dragover
event on an element, and then cancelling its default action, turns the element into a dropzone, i.e. it could accept drops of draggable items.
Going back with our last example, let's now add a new HTML element to act as the dropzone. We'll use a <section>
element, but you could use any element you like (such as a <div>
):
<div draggable="true">Drag and Drop</div>
<p>Dropzone</p>
<section></section>
div { /* same as above */ }
section {
min-height: 200px;
background-color: #eee;
max-width: 500px;
}
Dropzone:
At this stage, if we drag the <div>
above and take it into the <section>
element, we'll notice a 🛇 icon displayed. This indicates that the <section>
element doesn't accept any drops, i.e. it's not a dropzone.
In order to turn it into one, we need a dragover
handler. Let's set that up now:
var draggedElement;
var divElement = document.querySelector('div');
var sectionElement = document.querySelector('section');
divElement.ondragstart = function(e) {
draggedElement = e.target;
}
sectionElement.ondragover = function(e) {
e.preventDefault();
}
And now let's try performing the same drag action as we did above:
This time, thanks to the dragover
handler, as soon as the <div>
is taken inside the <section>
, we don't notice the 🛇 icon. This means that <section>
can now accept a drop.
The last thing left now is to handle the drop
event on the <section>
element so that we are able to actually move the dragged <div>
from its original position into the <section>
.
The drop
event
When a DnD operation successfully completes on a dropzone, as we drop an item over it, the dropzone element receives a drop
event.
drop
event fires on a dropzone as soon as a draggable element is dropped over it.This 'dropping' action refers to releasing the mouse button after a drag operation while the pointer is still over the dropzone. It's literally as if we 'drop' the dragged item over the dropzone.
Keep in mind that for drop
to occur on any element, it's a MUST to cancel the default behavior of the browser for the dragover
event on that element; omitting this step would simply keep drop
from firing!
Upon the occurrence of drop
, we could do numerous things. For example, if any file is dropped, we could maybe upload it to the server; if an image is dropped, we could showcase its preview; if any selected text is dropped, maybe we could process it; and so on
Going with our <div>
and <section>
example above, we'll remove the dragged <div>
element from where it is in the DOM tree, and instead place it inside the <section>
dropzone.
But how will we do this?
One way is to use removeChild()
to remove this node from its original position and then use appendChild()
to add it inside <section>
.
But we could shorten this set of statements using a very simple trick. Calling appendChild()
on an existing node automatically removes it from its original place — no need to do the removal manually.
Alright, so let's now handle the drop
event on <section>
and get this idea accomplished:
/* ... */
sectionElement.ondragover = function(e) {
e.preventDefault();
}
sectionElement.ondrop = function(e) {
this.appendChild(draggedElement);
}
As soon as the <div>
is dropped over <section>
, the drop
event gets fired and, consequently, the <div>
is moved from its original position to inside this dropzone.
Simple, isn't it?
We have a draggable <div>
element and a droppable <section>
element. The <div>
can be dragged-and-dropped into the <section>
, at which point it's moved from its original location in the document to inside this <section>
element.
This completes our basic and minimalistic DnD implementation.
Time to move over to a slightly more complex example.
A more complex example
In the example above, we had just one single draggable <div>
element. What's more interesting, however, is to have many of them, each one capable of being dragged-and-dropped onto the <section>
element.
Let's see how to implement this functionality.
First of all, note that no major changes would be required to be made in the previous code in order to implement this. We'll just need to create multiple <div>
s and assign each one the same dragstart
handler that we gave to the single <div>
in our last example.
Doesn't seem complicated, right?
To start with, let's set up the HTML. Previously we had one <div>
, now we'll have many of them — four, to be precise:
<div draggable="true">Div 1</div>
<div draggable="true">Div 2</div>
<div draggable="true">Div 3</div>
<div draggable="true">Div 4</div>
<p>Dropzone</p>
<section></section>
The CSS is the same as before. Just to put a little bit of spice, we'll give a different background color to each <div>
element:
<div draggable="true">Div 1</div>
<div draggable="true" style="background-color: #f5deb3;">Div 2</div>
<div draggable="true" style="background-color: #ffb5b5;">Div 3</div>
<div draggable="true" style="background-color: #aaaaff;">Div 4</div>
<p>Dropzone</p>
<section></section>
For the scripting part, we'll first fetch the list of all these <div>
elements inside the variable divElements
, removing the old divElement
variable:
var draggedElement;
var divElements = document.querySelectorAll('div');
var sectionElement = document.querySelector('section');
Then, we'll assign a dragstart
handler to each element in divElements
. This handler would be the same, so we'll create a named function for it:
var draggedElement;
var divElements = document.querySelectorAll('div');
var sectionElement = document.querySelector('section');
function dragStartHandler(e) {
draggedElement = e.target;
}
divElements.forEach(function(divElement) {
divElement.ondragstart = dragStartHandler;
})
Now before we proceed any further, notice one thing.
In the HTML above, we gave each <div>
element the draggable
attribute manually. Wouldn't it be a good idea if we could set this attribute from within JavaScript?
In this way, we won't have to manually set up the attribute on each element in the HTML.
So first, let's clean up our old HTML:
<div>Div 1</div>
<div style="background-color: #f5deb3;">Div 2</div>
<div style="background-color: #ffb5b5;">Div 3</div>
<div style="background-color: #aaaaff;">Div 4</div>
<p>Dropzone</p>
<section></section>
and then transfer the draggable
-setting logic inside forEach()
:
var draggedElement;
var divElements = document.querySelectorAll('div');
var sectionElement = document.querySelector('section');
function dragStartHandler(e) {
draggedElement = e.target;
}
divElements.forEach(function(e) {
e.draggable = true;
e.ondragstart = dragStartHandler;
})
With this done, let's resume from where we left.
So now that each <div>
element has a dragstart
handler registered, while keeping in mind that the <section>
element has the same event handlers as we saw before, it's time to try out the example live.
It works perfectly!
It's in this example that our old dragstart
handler makes sense.
Previously, we just had one <div>
element and so saving its reference inside the global variable draggedElement
(inside the dragstart
handler) was meaningless, since we already had a global variable divElement
pointing to the <div>
.
However, now the scenario is different. We have four <div>
elements, each with the same dragstart
handler. We can't possiblt know which one causes the event to fire and that's why we use the event's target
to obtain this piece of information, and ultimately assign its value to draggedElement
.
To summarize all this long discussion, using the DnD API provided by JavaScript, any given set of elements on a webpage can be given a custom drag-and-drop functionality.
Here's the minimal setup:
- Firstly, we set the
draggable
HTML attribute on the element to the value"true"
. This makes the element draggable, i.e. it could be dragged using the mouse pointer. - Then we handle the
dragstart
event on this draggable element. Inside the handler, we usually configure some data related to the ongoing drag. - Next, we configure an element into a dropzone by handling its
dragover
event and preventing its default action. - Finally, we handle the
drop
event on this dropzone. In the handler, we process the dropped item.
This is a very basic implementation of a custom DnD functionality.
More complicated workflows can be made by listening to a larger number of events and by setting the data associated with the drag using specialized utilities made for the task. In the upcoming chapters, we'll cover all these details one-by-one.
For now, it's important that you get firm on the very basics of DnD that we learnt in this chapter.
Once these concepts are peaches and cream for you, consider moving over to the next chapter. Otherwise, you'll find yourself not able to fully comprehend the DnD API.
Spread the word
Think that the content was awesome? Share it with your friends!
Join the community
Can't understand something related to the content? Get help from the community.