Introduction

Uptil this point, in this unit, we've covered a great deal of stuff. We started out with the basics of drag-and-drop, then got into the very details of draggable elements while considering the three events dragstart, drag, and dragend and many other related concepts; and then finally how to work with a drag's data store.

Not to mention, your foundations on drag-and-drop would have become exceptionally strong by this point. But the story doesn't end here. There is still a lot to cover in this DnD API.

In this chapter, we shall get a quick recap of 'dropzones'; see which elements, by default on a webpage, are dropzones; work with the events dragover and drop and two new events: dragenter and dragleave; and many many minute concepts related to dropzones, in general.

Let's begin!

A quick recap

Back in the Basics chapter, we got ourselves introduced to the concept of dropzones. In this section, we'll get a quick recap of this idea and a couple more related ones.

When we drag an item anywhere on our operating system, it eventually has to end with a drop. The 'drop' action occurs when the mouse button is released amid a drag operation.

If the place where the draggable item is dropped accepts drops, we call it a dropzone.

In simple terms,

A dropzone is an element that accepts drops.

Some other terms used to refer to a dropzone are drop target, droppable element, droppable target, droppable, destination element.

By default, nothing on a webpage is a droppable, except for the following:

  1. Text input boxes (given by <input type="text">)
  2. Textarea boxes (given by <textarea>)
  3. Any element with contenteditable set to "true".

Basically, wherever we have the ability to input stuff using the keyboard, it is a dropzone by default.

However, using the DnD API we could turn almost any element into a dropzone by listening to a couple of events.

An <img> element can't be made a dropzone.

The event that sits between a droppable and non-droppable target is dragover. It is what makes a droppable target droppable.

But how?

Let's see it in detail...

The dragover event

The dragover event fires repeatedly on an element as the mouse pointer is brought over it amid an ongoing drag operation.

It stops the moment the mouse pointer exits the element.

By default, the dragover handler for all elements is configured to cancel any subsequent events from firing on the element.

This means that, by default, the drop event, which fires after dragover, never gets dispatched due to the native mechanism of the browser for handling dragover.

Fortunately, we could prevent this from happening by setting up a custom handler and stopping the default action from getting executed.

An example follows:

<p>The element below is a dropzone.</p>

<section></section>
section {
   min-height: 200px;
   background-color: #eee;
   max-width: 500px;
}

We have a <section> element which we would be turning into a dropzone. Here's how it looks initially:

The element below is a dropzone.

To make the element a dropzone, we'll simply handle its dragover event and call e.preventDefault() in there. There's no need to execute any other statement in here (though, we could, as we wish), so why worry about giving one?

var sectionEle = document.querySelector('section');

sectionEle.ondragover = function(e) {
   e.preventDefault();
}

With this set, now our <section> element has become a dropzone.

The element below is a dropzone.

You might say: How do I know whether the <section> element is a dropzone?

Well, try dragging a text selection into the <section> element and see the pointer shown. Is it any different from the one shown previously?

This is the first hint for the fact that the element is a dropzone.

But the real interest comes after we start handling the drop event.

Handling dragover is a MUST for drop to fire!

Handling drop ain't possible without setting up the minimal dragover handler shown above.

But why?

Without cancelling dragover, as we did in the last example above, a subsequent drop event won't ever fire. Hence, the respective element won't ever be able to accept drops.

But with the dragover handler set and its default action prevented, a subsequent drop event would be able to take place on the element, and hence turn it into a dropzone.

Usually, nothing is required on the occurence of dragover, except for simply cancelling the event's default action.

Moving on, since the dragover event fires very quickly at regular intervals, it's recommended NOT to put complicated code in its handler.

Doing so could wreak havoc on low-end devices, sometimes even high-end devices. If you really need to perform a complicated task in here (which we think no developer would ever need to) then you should consider throttling or debouncing the event's handler.

Logically, the next event to unravel after dragover is drop.

Let's move on to it...

The drop event

By far, the most awaited event of the whole DnD API is drop.

The drop event occurs on an element right after dragover when a dragged item is brought over it and the mouse button is released in the meanwhile.

Most of the bulk of work of a custom DnD functionality is performed in the drop's handler. This includes showing files, images, text, whatever is dropped, or throwing errors if the dragged element is not supported to be displayed in the dropzone.

As always, let's set up an example to demonstrate the drop event.

For the first one, we'll configure the handler for drop such that it removes the dragged element from its original location and adds it inside the dropzone.

You know how to accomplish this!

<div draggable="true">Drag and Drop</div>

<section></section>
The CSS styles for this example are the same as used throughout the previous DnD API — Drag Data chapter.
var draggedEle;
var divEle = document.querySelector('div');
var sectionEle = document.querySelector('section');

divEle.ondragstart = function(e) {
   draggedEle = this;
}

sectionEle.ondragover = function(e) {
   e.preventDefault();
}

sectionEle.ondrop = function(e) {
   this.appendChild(draggedEle);
}

Live Example

This was basic!

For the second example, keeping the HTML (and the CSS) the same, we'll configure the ondrop handler such that it copies the dragged element inside it:

var draggedEle;
var divEle = document.querySelector('div');
var sectionEle = document.querySelector('section');

divEle.ondragstart = function(e) {
   draggedEle = this;
}

sectionEle.ondragover = function(e) {
   e.preventDefault();
}

sectionEle.ondrop = function(e) {
   this.innerHTML += draggedEle.outerHTML;
}

Live Example

Yet, another simple functionality.

For the third example, we'll do a little bit more work.

We'll create a <section> dropzone which would only accept stuff that has some drag data of type text/uri-list set on it.

As soon as such an item is dropped over this dropzone, a new <a> element is created, its href and content gets set to the URL stored in the drag data store, and finally appended to the dropzone.

First thing's first, let's set up the desired HTML and CSS:

<p>Put links here:</p>

<section></section>
section {
   min-height: 200px;
   background-color: #eee;
   max-width: 500px;
}
section a {
   display: block;
   margin: 5px
}

The declarations for section a are given to make each link appear on a newline with enough whitespace to breathe in.

Put links here:

With this done, it's time to set up the logic:

var sectionEle = document.querySelector('section');

sectionEle.ondragover = function(e) {
   e.preventDefault();
}

sectionEle.ondrop = function(e) {
   var url = e.dataTransfer.getData('text/uri-list');
   if (url) {
      var a = document.createElement('a');
      a.href = url;
      a.innerHTML = url;
      this.appendChild(a);
   }
}

First inside the drop's handler, we check whether the drag's data store has some data of the format text/uri-list. If it has one, we create a new <a> element and set its href and innerHTML to the value of this text/uri-list data.

Here, we could say that the dropzone <section> is acting as a storage area for links.

Let's try this live.

Live Example

A bit more involved, but still simple!

Moving on, akin to all drag events we've seen so far, the drop event could be handled on the global window object as well. And before that, a global handler for dragover is required in order to cancel the default action for the event.

Consider the code below:

window.ondragover = function(e) {
   e.preventDefault();
}

window.ondrop = function(e) {
   // drop handling code
}

This makes the whole HTML document a dropzone.

Usually, it is not recommended to do this but if you think you really need to listen to drop on the whole document, then go for handling it (and dragover as well) on window.

Once again, make sure not to execute any complicated, resource-intensive, routine inside the handler for dragover on the window object.

dragenter and dragleave events

Both the events dragover and drop were already familiar to you from the Drag and Drop Basics chapter, and so hopefully you might not have had a hard time understanding them.

The DnD API provides two other method which fire on a dropzone as stuff is dragged into and out of it.

They are dragenter and dragleave, respectively.

dragenter fires once when the item is dragged into the dropzone.
dragleave fires once when the item is dragged out of the dropzone.

The names of both these events are pretty much self-explanatory as to when exactly do the they fire. Let's understand the naming...

As soon as an item, being dragged, enters an element, dragenter gets dispatched on the element, and as soon as the item leaves the element, dragleave gets dispatched on the element.

Now the thing is that do these events have any practical importance or not?

A huge yes!

dragenter and dragleave could be handled to toggle the styles of a dropzone to visually indicate to the user that the element could accept the item being dragged.

For intance, on dragenter we could reduce the opacity of the dropzone to a low value, and then restore it back to 1 on dragleave. This would make the dropzone respond interactively on hover.

There is no other way to accomplish this without dragenter and dragleave in the whole DnD API.

Alright, time for a quick example.

Below we have a draggable element and a dropzone:

<div draggable="true">Drag and Drop</div>

<section></section>
var draggedEle;
var divEle = document.querySelector('div');
var sectionEle = document.querySelector('section');

divEle.ondragstart = function(e) { draggedEle = this; }

divEle.ondragend = function(e) {
   sectionEle.style.opacity = '1';
}

sectionEle.ondragenter = function(e) {
   this.style.opacity = '0.3';
}

sectionEle.ondragleave = function(e) {
   this.style.opacity = '1';
}

sectionEle.ondragover = function(e) { e.preventDefault(); }
sectionEle.ondrop = function(e) { this.innerHTML += draggedEle.outerHTML; }

Almost everything here is pretty much standard as to what we've been doing so far. Only three things are of particular interest to us: lines 7-9, 11-13 and 15-17.

In lines 11-13 and 15-17, the dragenter and dragleave events are being handled on the <section> element, respectively. Both these handlers toggle between opacity values for the element, making it seem interactive when stuff is dragged over it.

In lines 7-9, we handle dragend on the <div> element. This is necessary because if the dragged <div> is dropped onto the dropzone, dragleave would never fire and hence the dropzone would become set to opacity: 0.3. In order to reset it back to its original value in this case, we have to listen to dragend and accomplish the opacity reset there.

Live Example

Let's consider another example.

This time we toggle between a class on the dropzone element upon the dragenter and dragleave events. The class when added, sets up a keyframe animation on the element, which in turn, zooms it in and out continuously.

.zoom {
   animation: zoomInOut 0.4s ease-in-out infinite alternate
}

@keyframes zoomInOut {
   to {
      transform: scale(1.1);
   }
}
To learn more about CSS animations, refer to our guide on CSS Animations Basics.
var draggedEle;
var divEle = document.querySelector('div');
var sectionEle = document.querySelector('section');

divEle.ondragstart = function(e) { draggedEle = this; }

divEle.ondragend = function(e) {
   sectionEle.classList.remove('zoom');
}

sectionEle.ondragenter = function(e) {
   this.classList.add('zoom');
}

sectionEle.ondragleave = function(e) {
   this.classList.remove('zoom');
}

sectionEle.ondragover = function(e) { e.preventDefault(); }
sectionEle.ondrop = function(e) { this.innerHTML += draggedEle.outerHTML; }

Live Example

Simple, isn't it?

Usually, the events dragenter and dragleave are both handled together; it's rarely ever needed to just handle one event.

And one thing more — be wary of the dispatch of dragenter and dragleave events. Sometimes, they could fire in unexpected ways. The following section explains this.

Unexpected dragenter and dragleave dispatch

One strange behavior in regards to dragleave is that the event fires on a dropzone as soon as the pointer goes over one of its child elements within it.

Ideally, the event shouldn't fire unless the pointer goes out of the whole dropzone element.

However, as in the current implementation, it fires when the pointer goes over a child element, even if that child element is within the dropzone.

And before this, dragenter fires over that child element.

Does this flow of event dispatch resemble any other set of events in JavaScript?

Well yes, it sure does!

dragenter and dragleave work pretty much like mouseover and mouseout.