Native Drag and Drop

by .

Along with an army of JavaScript APIs, HTML 5 comes with a Drag and Drop (DnD) API that brings native DnD support to the browser making it much easier to code up.

HTML 5 DnD is based on Microsoft’s original implementation which was available as early as Internet Explorer 5! Now currently supported in IE, Firefox 3.5 and Safari 4.

We’re going to look how to implement DnD and review some of the issues (or hoops we have to jump through) with the current implementations now.

My First Drag and Drop

DnD requires only a few things to work:

  • Something to drag
  • A drop target
  • JavaScript event handlers on the target to tell the browser it can drop

The following elements are draggable by default: <img> elements and <a> elements (with an href).

If you want to drag a different element, you need to set the draggable attribute to true. That’s according to the spec, but to get it to work in Safari and Firefox there’s a little more that you need to do, which we’ll come on to.

If you want to skip the code walk through, here’s the simple drag and drop (source code)

Here’s our example markup:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset=utf-8 />
<title>Basic Drag and Drop</title>
<style>
#drop {
  min-height: 100px;
  width: 200px;
  border: 3px dashed #ccc;
  margin: 10px;
  padding: 10px;
}
p {
  margin: 3px 0;
}
</style>
<script src="http://html5demos.com/h5utils.js"></script>
</head>
<body>
  <img src="http://twitter.com/api/users/profile_image/rem" alt="Remy Sharp" />
  <img src="http://twitter.com/api/users/profile_image/brucel" alt="Bruce Lawson" />
  <img src="http://twitter.com/api/users/profile_image/Rich_Clark" alt="Rich Clark" />
  <div id="drop"></div>
</body>
</html>

Note: the included h5utils.js script is a small library to trigger HTML 5 elements and to give me cross browser event binding.

Currently the <img> elements can be dragged, but they can’t be dropped (this is the browser’s default).

To drop elements we need to:

  1. Tell the browser that the element can be dragged over a specific point
  2. Handle the drop event

To tell the browser we can drop in this element, all we have to do is cancel the dragover event. However, since IE behaves differently, we need to do the same thing for the dragenter event.

Here’s the code for basic drop handling:

var drop = document.querySelector('#drop');

// Tells the browser that we *can* drop on this target
addEvent(drop, 'dragover', cancel);
addEvent(drop, 'dragenter', cancel);

addEvent(drop, 'drop', function (event) {
  // stops the browser from redirecting off to the text.
  if (event.preventDefault) {
    event.preventDefault(); 
  }

  this.innerHTML += '<p>' + event.dataTransfer.getData('Text') + '</p>';

  return false;
});

function cancel(event) {
  if (event.preventDefault) {
    event.preventDefault();
  }
  return false;
}

Note for this code, I’m using addEvent where you’d normally see element.addEventListener, it’s from the h5utils.js library to support IE.

In the JavaScript we are:

  1. Searching for the drop target in the DOM using document.querySelector (which returns the first result only)
  2. When dragover event is fired (when the user drags the element over another), it will trigger the function called ‘cancel’ (at the bottom of the code above) which will prevent the default browser action
  3. Do the same for dragenter to support IE
  4. Bind the drop event, and within there grab some data about what was dropped.

dragover & dragenter

By cancelling this event, we’re telling the browser this element that we’re over is the one you can release and drop upon.

I’m still not entirely sure why there’s a difference here, but Firefox and friends needs preventDefault on the event, and IE requires the return false. Note that the examples out in the wild that use inline JavaScript (yuk, bad), the preventDefault is supposed to be implicit, so you won’t see it in the code.

drop

To show what was dropped, we need to do two things:

  1. Cancel the default browser action. This is to ensure that if we drop a link, the browser doesn’t go off and surf to that location
  2. Get the contents out of the dataTransfer object

The cancelling of the browser default is, again, done using the preventDefault and return false.

If we don’t set the dataTransfer data manually (which I’ll show you later), the default key is set to Text. So this might be the value of an href of an <a> element, or it might be <img> src address.

Doing More with Drag’n Drop

There’s a number of extra bits in the spec other than just dragging images.

Here’s a few bits that I can see being useful:

  1. Being able to drag any element
  2. More complex data types, other than text
  3. Set a different drag icon/image when dragging the element

Dragging Anything

If we change the <img> to <div> tags with background images, I still want to be able to drag them around the page, and drop them in to my container.

The HTML 5 spec says it should be as simple as adding the following attributes to the markup of the elements in question:

draggable="true"

However, this doesn’t work completely for Safari or Firefox.

For Safari you need to add the following style to the element:

[draggable=true] {
  -khtml-user-drag: element;
}

This will start working in Safari, and as you drag it will set a default, empty value with the dataTransfer object. However, Firefox won’t allow you to drag the element unless you manually set some data to go with it.

To solve this, we need a dragstart event handler, and we’ll give it some data to be dragged around with:

var dragItems = document.querySelectorAll('[draggable=true]');

for (var i = 0; i < dragItems.length; i++) {
  addEvent(dragItems[i], 'dragstart', function (event) {
    // store the ID of the element, and collect it on the drop later on
    event.dataTransfer.setData('Text', this.id);
  });
}

Here’s a working demo of drag and drop anything (source code)

More Complex Data Types

You’ve already seen how we can use dataTransfer.setData(format, string) to associate some data in the example above.

You can also use this key/value pair to store more data, but you’re limited to storing strings only.

One way I would get around this is to have a data lookup, where the key of the lookup may be the ID of the element, and then I can de-reference the data on the drop event.

For example:

var people = {
  rem : { 
    name : "Remy Sharp",
    blog : "http://remysharp.com"
  },
  brucel : {
    name : "Bruce Lawson",
    blog : "http://brucelawson.co.uk"
  }
  // etc...
}

var dragItems = document.querySelectorAll('[draggable=true]');

for (var i = 0; i < dragItems.length; i++) {
  addEvent(dragItems[i], 'dragstart', function (event) {
    // store the ID of the element, and collect it on the drop later on
    event.dataTransfer.setData('Text', this.id);
  });
}

addEvent(drop, 'drop', function (event) {
  // stops the browser from redirecting off to the text.
  if (event.preventDefault) {
    event.preventDefault(); 
  }

  var person = people[event.dataTransfer.getData('Text')];

  this.innerHTML += '<p><a href="' + person.blog + ">' + person.name + '</a></p>';

  return false;
});

Here’s a working demo of drag and drop with associated data (source code)

Obviously this isn’t the most ideal situation, but there’s also the possibility to set other content types in the drag data, which should produce some interesting apps in the future.

Drag Icon

Along with several other options with the dragstart event, you can also set a drag image, i.e. what you see under your cursor.

You can create a DOM fragment, and then associate the fragment with the dataTransfer using setDragImage(element, x, y).

So we can add the following to our example to use a custom drag image:

var dragIcon = document.createElement('img');
dragIcon.src = 'http://twitter.com/api/users/profile_image/twitter?size=mini';

Which creates an image element, and how within the dragstart event, we’ll set the drag image:

addEvent(dragItems[i], 'dragstart', function (event) {
  // store the ID of the element, and collect it on the drop later on
  event.dataTransfer.setData('Text', this.id);
  event.dataTransfer.setDragImage(dragIcon, -10, -10);
  return false;
});

This sets the custom drag icon 10 pixels below the cursor, as seen in this example: drag and drop with custom image (source code)

Native Drag

There’s lots of Drag and Drop JavaScript libraries available, but what I’d like to see, is native DnD support falling back to library based. However, I know for a fact that some libraries, including jQuery, construct a custom event when passed in to the event handler which means, currently, you can’t use the dataTransfer object, so you’ll have to rely on binding the events yourself. I’m sure this will change soon.

There’s lots more in Drag and Drop but this should be enough to go and play with now!

As for me, I'll be updating the Drag and Drop demos on HTML5demos.com to see if I can add ARIA support and capture a screencast of it running. Watch this space!

Further Reader

82 Responses on the article “Native Drag and Drop”

kl says

I’d love if that allowed upload of dropped images (get image from your desktop, drop it onto your avatar, done!)

[...] Native Drag and Drop | HTML5 Doctor Along with an army of JavaScript APIs, HTML 5 comes with a Drag and Drop (DnD) API that brings native DnD support to the browser making it much easier to code up. HTML 5 DnD is based on Microsoft’s original implementation which was available as early as Internet Explorer 5! Now currently supported in IE, Firefox 3.5 and Safari 4. We’re going to look how to implement DnD and review some of the issues (or hoops we have to jump through) with the current implementations now. [...]

SlexAxton says

I’m still not sure that I entirely agree with the whole ‘return false/preventDefault’ thing to get the drop event to fire. Is there a parallel in any other system of events? Just like we don’t have to preventDefault on the mouseUp event in order to fire the click event. :(

Remy Sharp says

@SlexAxton – I know what you mean. After reading the specs over and over (along with some cross reading over at the MDC), I kinda understand the logic.

What it’s telling the browser is that this element is the one we want to catch the drag event against, otherwise the browser goes ahead does it’s normal drag action. So by cancelling the event, it tells the browser that this is the element that should start moving.

Do you see what I mean?

Nick Fletcher says

Remy: That’s not quite right. The use of preventDefault() is to stop the default action of the browser. If you want it to not bubble then you would use stopPropagation().

Remy Sharp says

@Nick – sorry you’re right, that was an extended typo in my comment – I’ve updated it, cheers.

Use the HTML5 Doctype - Not Just a Hat Rack says

[...] to implement some of the HTML5 components that are currently supported cross-browser. You can use native drag and drop, “block-level” links, and the new, more semantic elements with an HTML5 doctype now, [...]

Not Just a Hat Rack - Must-Read HTML5 Articles says

[...] has some articles on things that are mostly cross-browser, and more or less safe to use now, like Native Drag and Drop and “Block-level” links. Unfortunately I can’t recommend utilizing much more of the tips [...]

Dissertation Project | NDE Designs says

[...] shouldn’t be too hard to work out how to do considering I have quite a few resources that I can go to which explain how to do it and will hopefully provide some support if I need [...]

穿過雲層оО Rudy | HTML5学习资源整理 says

[...] the HTML 5 Canvas element works HTML 5 canvas A demo of how the Drag-n-Drop functionality works. Native Drag and Drop A Mozilla Labs experiment on how to build an extensible Web code editor using HTML 5 technology. [...]

[...] Drag & Drop I think it would be cool to drag and drop products in to the basket. HTML5 has the ability to create drag and drop events. This functionality has been recently added to Gmail. [...]

Matias Larsson says

Do you have any suggestions on how to handle the case when the drop target is not visible in the viewport requiring the user to scroll the page to see it?

It doesn’t seem like the HTML5 spec mentions auto-scrolling the document when the dragged element is near the edges of the window (which is the normal way to do it).

Of the major browsers, only IE8 does some kind of auto-scrolling (although in an extremely weird fashion, which makes it kind of useless anyway).

Matias Larsson says

I have to add that all browsers seem to stop firing mousemove events when doing native drag and drop. Haven’t done any deep research on the subject though.

Let’s HTML5 | Peter Mikhael says

[...] Native Drag and Drop – A demo of how the Drag-n-Drop functionality works. [...]

Tutorial: Drag Drop with HTML5 « HTML5 Magazin says

[...] Plugin für WordPress »Tutorial: Drag Drop with HTML5Von KHK, am 9. Juli 2010Auf html5doctor.com wird ein ausgezeichnetes Drag Drop with HTML5 Tutorial angeboten. Man erfährt anhand von [...]

Jerome Covington says

I’m trying to capture the id of the dropped item. Take a moment to look at the question I submitted to StackOverflow. Seems native drag ‘n’ drop is a bit of a mystery, still, to the dev community there.

http://stackoverflow.com/questions/3893927/native-drag-and-drop-check-for-dropped-items-id


function dragDrop(itemId){

var droppable = document.querySelector('#droppableElement');

addEvent(droppable, 'drop', function(e){
if (e.preventDefault){
e.preventDefault();
}
// How do I check that the id of the dropped item === itemId
if (e.dataTransfer.getData('id') === itemId){
alert('Successfuly dropped item with id = ' itemId + '.');
}
});
}

draggableItem = document.querySelector('#draggable');
dragDrop(draggableItem);

The comment is where I need help.

Brendan says

However, I know for a fact that some libraries, including jQuery, construct a custom event when passed in to the event handler which means, currently, you can’t use the dataTransfer object, so you’ll have to rely on binding the events yourself. I’m sure this will change soon.

In jQuery you can access the dataTransfer object through event.originalEvent.dataTransfer which allows you to use the usual bind, live, delegate etc. methods of adding Events.

Alexander says

@ Comment by Matias Larsson at May 18th, 2010 at 5:32 pm ( http://html5doctor.com/native-drag-and-drop/#comment-3712 )

(I didn’t read all the comments, so pardon me if I’m repeating something another person might have written before me)

The case with mousemove events not firing during drag operations is something I’ve also encountered a while back in my first VCL C++ programming sessions. I suppose it’s a default OS behavior that nobody has decided to override so far (suppose it’s not that easy either, whoever tries will probably have to implement their own dragging functionality from scratch, while successfully being compatible with the default OS functionality)… Of course, now I’m only referring to versions of Windows, because this is the only system on which I’ve had the chance to test this occurrence (with C++) so far. For that reason, I’m not surprised to see this show up in JS as well, considering your input.

The good thing in the C++ programs is, however, that you can use the OnDragOver event there to get the mouse’s X and Y coordinates relative to the current form as this method fires repeatedly, like onMouseMove (in my case this event was bound to the form object). Additionally, after you drop the element you were dragging so far, one onMouseMove element would be fired, even if you didn’t actually move the mouse after dropping, which I suppose is to update on the final mouse position when you drop, so the program code that was using this event could pick up from there. I’m guessing, there must be some kind of similar functionality available for JavaScript applications.

P.S. I’ve spent some minutes now reading the W3 spec, but I don’t seem to see this described anywhere. Maybe I’m just missing it.

Matias Larsson says

@Alexander, thanks for your input! I’ll test your theory when I get the time.

As a work-around I added two “invisible” scroll zones and bound the dragover and drop events to them (example in jQuery). Not the prettiest solution, but at least it gets the work done.


$(".scroll-zone").bind("dragover drop", function (e) {
var dt = e.originalEvent.dataTransfer;
dt.dropEffect = "none"; // We should not be able to drop on the scroll area
switch (e.type) {
case "dragover":
if ($(e.target).hasClass("up")) {
window.scrollBy(0, -40);
}
else {
window.scrollBy(0, 40);
}
return false;
}
});

Introduction to html5 says

[...] Native Drag and Drop – A demo of how the Drag-n-Drop functionality works. [...]

Leo says

may be I’m doing something wrong but is not working for me: I already try on FF4, chrome and safari for Windows.

web design uae says

Thanks.I would like to implement it in my site.

Beth Budwig says

As for Leo, the demos totally failed to work for me. Good article though!

Mark says

It appears that the links to the images are no longer active.

swapnil says

If i want to show images on canvas dragged from div then how it could be done?
i have done that using div to div drag & drop of image, but on canvas it was drop also but after drop effect it disappear. So any one knows proper answer guys ?

shivani says

Hi.I have one doubt.I want to drag arc from canvas in html5 .Is there any way to setAttribute for arc

Hadi says

I tried this code with Iphone but it din’t work. dose that works with smartphones?

Thanks.

xDelph says

It works well for me but i have a problem

I want to cancel a live drag with a timer (the user take too long to drop for example) but it doesn’t want to work (even the escape key press doesn’t work)

can someone help me ?

I use : last Chrome version only, HTML5, Javascript, jQuery

jhansi says

hi remy,
your demo examples are not working in firefox!!!

Remy Sharp says

Hi jhansi!!!

Sorry!!! The h5utils.js file was moved so it wasn’t working in any browser!!! It works now!!!

Cheers!!!

jack says

hi, I am adding the elements dynamically to my DOM and trying to register the ‘dragstart’ event and set the datatransfer. But I couldn’t find the element after adding into the DOM, so ‘dragstart’ event is also not registered. So is there any other way of doing DragDrop for dynamically added elements?

sandeep says

hai buddy can we place the same concept inside the canvas

Mike Hampton says

This helped me immensely to get some things working in Firefox, however; the example (http://jsbin.com/uzovu/1357) does not work in Internet Explorer (9.0.8112.16421) for me (big surprise).

I know the article was written three years ago, but is there any chance you know of changes that might have affected IE?

Thank you for a great article.

Alexander says

@Mike Hampton

Your jsbin example has one crucial portability problem – the use of document.querySelectorAll

in MSIE 9, at least with Quirks mode on (as it is in the example) the statement (document.querySelectorAll === undefined) is true, which makes the script break. You can debug it further with the F12 dev tools if you like.

If you want it fixed in MSIE 9, you need to either: get your hands on a doctype (so, it would no longer be in Quirks mode), use a different (and more browser-compatible) way to discover draggable elements, or use jQuery (or any other JS library with similar features) instead.

Mike Hampton says

Thank you Alexander, however; it is not my example. It is the example from this article

“Here’s a working demo of drag and drop anything (source code)”

Tom Chiverton says

In IE8 I only get ‘null’ appearing when I drop.

Andrew says

I’m always happy when I can beat IE’s limitations and struggle on to the next problem (currently IE jsonp error responses).

This DnD example helped me successfully drop external links onto an Explorer window so I can process them. What I cannot fathom out though is why the event occurs twice? Has anyone else seen this happening?

sylvain says

Hello,

I checked “drag icon” demo on different browsers. It works fine for most of them except for IE10.
on IE10, default image is used (instead of twitter icon). So, it seems setDragImage is inoperative for IE. Am I wrong ? Which workaround can be used for IE ? any idea ?

Thank you for this article

Best Regards
Sylvain

Ken Hikage says

Just wanted to note that your advanced example is missing a ‘.


this.innerHTML += '<a href="' + person.blog + '" rel="nofollow">' + person.name + '</a>';

Martijn says

Just letting you know you can now do this with jQuery, since it allows access to the native event object via the originalEvent property.

Janp says

How can I clear the text block each time I drag and drop an image.

Jim says

Most drag and drop tutorials explain everything perfectly, including yours, but nowhere can I find how to identify the source div (this is the div which is vacated in drag and drop), so that I can compare it with the target div with a view to allowing or preventing a drop onto a certain element.
I have set up a chequers board, playing squares as per chequers by using CSS boxes. I have four red pieces (drag1, drag2 and so on) and one green piece (drag5).
The drag5 piece can be moved x + or – the width of a square and y + or – the height of a square. (In other words up or down the board.) The other four pieces can move only down the board, i.e.,
x + or – one square width and y + one square height.
At present I can move any piece onto any playing square.
If a piece were to be moved from square 6 my math would be:
if source – target = -3, -4, +4 or +5 green could be moved onto any of squares 2, 3, 10 or 11;
if source – target = +4 or +5 a red piece could be moved onto only 10 or 11.
I also need to prevent moves to an already occupied square and to control the sequence of moves (green, red, green…….) with a counter.
I am a teacher of English as a foreign language and need to create suitable materials such as games, multi-choice quizzes for my foreign students.
Your help will be greatly appreciated!

GR says

You don’t need all that mumbo jumbo to drag & drop!
Just a couple of lines of code will do…

function MoveItem(Which){
X=event.clientX+window.pageXOffset;
Y=event.clientY+window.pageYOffset;
document.getElementById(Which).style.left=X+’px’;
document.getElementById(Which).style.top=Y+’px’;
}

Hello

Join the discussion.

Some HTML is ok

You can use these tags:
<a href="" title="">
<abbr title="">
<b>
<blockquote cite="">
<cite>
<del datetime="">
<em>
<i>
<q cite="">
<strong>

You can also use <code>, and remember to use &lt; and &gt; for brackets.