HTML 5 drag and drop API

The native HTML5 drag-and-drop API behaves differently across browsers and due to this can be tough to implement. And while many of the browser inconsistencies are not likely to be mitigated in the near future, one still has to use the API on occasion. It comes up when dragging files from the desktop to an application and dragging objects between multiple windows. The following article documents a number of the browser inconsistencies and helps you pick the best progressive enhancement strategy. Tutorials and specs can be found in the references section.

Making element draggable

To make an element draggable set draggable attribute to "true". However this is not enough to make elements draggable in Firefox. You need to locate the dragstart event and set some data on the DataTransfer object:

draggableElement.addEventListener('dragstart', function(e){
  e.dataTransfer.setData('text', 'foo');
});

That should do it! Now you can drag elements in all browsers.

I am draggable

Drag-related events

The

dragstart, drag and dragend events are fired on draggable element during drag operation.
dragstart event is fired when the drag operation has started;
the dragend fires when the drag operation has completed; either on a valid drop target or not. The
drag event is fired dozens of times per second between the dragstart and dragend events.

Drag me to count drag events

DataTransfer object and setData method

DataTransfer object is used to hold the data during drag and drop operations. Data is added to the DataTransfer object using the setData(type, data) method. This method must be called in dragstart event handler.

Data you've set with setData is protected and will only be exposed to drop target on drop event. This is in place to prevent third-party iframes from accessing your data if you happen to drag over them.

Theoretically, DataTransfer should be able to hold multiple data items of various types:

var data = JSON.stringify({ foo: 'bar' });
event.dataTransfer.setData('application/json', data);
event.dataTransfer.setData('text/plain', 'foo=bar');

However in practice, Internet Explorer only supports 'text' and 'URL' for the 'type' argument. If you try setting type to anything else, IE will throw an exception. The unfortunate consequence of this incompatibility is that if the type of your dataTransfer is 'text', all text and input areas will by default behave as drop areas .

My data-type is 'text'. I interfere with inputs and text areas.
My data-type is 'application/json'. This results in an exception and I crash IE.
Data-type workaround for IE

The only way to gate this is to add a try-catch around setData and fall back to 'text'if you catch an exception. Help promote this bug in IE feedback center to get it fixed sooner.

Issue about dataTransfer.setData in IE feedback center

Custom drag image

Another useful feature of drag-and-drop API is the setDragImage(elt, xOffset, yOffset). It allows you to use a different visual representation of the element being dragged. Despite it's name, it can accept arbitrary HTML elements as arguments.

The only requirement is that the element being dragged must be visible to the browser when the drag operation starts. Chrome and Opera do not allow use of offscreen element (style.top = -1000px). But you can place the drag image under a non-transparent element and it will work just fine.

elt.addEventListener('dragstart', function(e){
  e.dataTransfer.setDragImage(document.querySelector('#dragImage'), 0, 0);
});
I have custom dragImage. I don't work in IE.

Internet Explorer doesn't support setDragImage at all.

Workaround for IE

A workaround for this issue in Internet Explorer is to create a DOM element with position: fixed and move it aroung on mousemove. You will also need to disable all pointer events for drag image, otherwise it will interfere with drag events. In IE9 pointer events are supported only by SVG element, so your drag image cannot be an arbitrary element.

Issue about setDragImage in IE connect center

Making element a drop zone

To make an element a drop zone you will need to follow a few unobvious steps. First of all, you need to prevent the default action for dragenter and dragover events to occur on designated drop targets. The default action is to cancel the drop operation.

The default in Firefox is to treat data in dataTransfer as a URL and perform navigation on it, even if the data type used is ‘text’. To bypass this, you will need to capture and prevent the drop event.

I am draggable
Drop zone

Dragging files from the desktop

This is the most enjoyable part of the drag and drop API. You can access files in the drop event via the event.dataTransfer.files property. Keep in mind that files is FileList and individual files can be accessed through the index. This property is supported consistently across all browsers and is the raison d'être of the whole drag and drop API.

Drop files here

dropEffect and effectAllowed

DropEffect must be set on the draggable element at the beggining of the dragstart event. The dropEffect can be either 'move', 'copy', 'link', or 'none'. Drop effect affects the cursor when you move an element over the drop target.

In theory, you can also provide effectAllowed for a drop target. If the drop effect of a draggable element is not allowed on a particular target, the drag operation will simply be cancelled.

However in practice, this does not work in Safari and IE. The drop event in these browsers is not prevented, regardless of the effects you set to drag and drop elements. You could hold off on using the drop effect API and look into finding out whether a particular operation is permitted by different means.

I can be copied and moved I can only be linked
I support move
I support copy
I support link

Dragging and scrolling

If the drop zone is far below or above the draggable element, you may want the document to scroll automatically. For example, scroll to the top of this article and try dragging one of the draggables from there to the drop target near the bottom of the page. Chrome, IE and Opera will auto-scroll for you. However in Firefox and Safari you will have to either use the scroll wheel or implement custom scrolling logic. For instance, you can change the scrollTop when a draggable is held near the edge of the scroll container.

Drop-target related events

Following events are fired on drop target: dragenter, dragover, dragleave, drop.

The dragenter and dragover must be prevented to enable utilizing an element as a drop zone;
dragover is fired at a certain rate while a draggable is above the drop zone;
dragleave is fired when the cursor leaves the drop target;

Unfortunately, both dragenter and dragleave events are not consistent with other mouse events. For instance, the dragleave event on the previous drop target is fired after dragenter is on a new target. So don't rely on these events to help detect the current drop target. Especially in Firefox, in which these events are not implemented as well.

I am draggable
This is drop zone with children:
First child
Second child

Custom cursor for draggable elements

You can set custom cursor for draggable element, however it is reset to default when any drag operation is started. Drop effect can be used as a limited adjustment for this.

Firefox is the only browser that pertains cursor on an element with :active pseudoclass during drag operation.

There are also multiple custom cursors available for drag operation: -webkit-grab, -webkit-grabbing, -moz-grab and -moz-grabbing.

Summary

The drag and drop API has limited application and its implementation varies greatly across different browsers. Additionally, the API is not supported on mobile devices.

Ineternet Explorer of the drag and drop API implementation is non-compliant with the spec and there are some currently outstanding issues their team is working through.

The native drag and drop API is usefull at times when (a) you need to drag objects across different browser windows and applications or (b) you need to drag files from the desktop. Otherwise you might want to consider a mouse-events based alternative.

References

  1. Drag-and-drop API spec (MDN)
  2. Tutorial on HTML5 rocks
  3. Blog post about drag-and-drop on MSDN
  4. Native HTML5 drag and drop on Inkling
  5. The HTML5 drag and drop disaster
Andrey Mereskin, 20 July 2014