Mozilla

HTML5 drag and drop in Firefox 3.5

This post is from Les Orchard, who works on Mozilla’s web development team.

Introduction

Drag and drop is one of the most fundamental interactions afforded by graphical user interfaces. In one gesture, it allows users to pair the selection of an object with the execution of an action, often including a second object in the operation. It’s a simple yet powerful UI concept used to support copying, list reordering, deletion (ala the Trash / Recycle Bin), and even the creation of link relationships.

Since it’s so fundamental, offering drag and drop in web applications has been a no-brainer ever since browsers first offered mouse events in DHTML. But, although mousedown, mousemove, and mouseup made it possible, the implementation has been limited to the bounds of the browser window. Additionally, since these events refer only to the object being dragged, there’s a challenge to find the subject of the drop when the interaction is completed.

Of course, that doesn’t prevent most modern JavaScript frameworks from abstracting away most of the problems and throwing in some flourishes while they’re at it. But, wouldn’t it be nice if browsers offered first-class support for drag and drop, and maybe even extended it beyond the window sandbox?

As it turns out, this very wish is answered by the HTML 5 specification section on new drag-and-drop events, and Firefox 3.5 includes an implementation of those events.

If you want to jump straight to the code, I’ve put together some simple demos of the new events.

I’ve even scratched an itch of my own and built the beginnings of an outline editor, where every draggable element is also a drop target—of which there could be dozens to hundreds in a complex document, something that gave me some minor hair-tearing moments in the past while trying to make do with plain old mouse events.

And, all the above can be downloaded or cloned from a GitHub repository I’ve created expecially for this article.

The New Drag and Drop Events

So, with no further ado, here are the new drag and drop events, in roughly the order you might expect to see them fired:

dragstart
A drag has been initiated, with the dragged element as the event target.
drag
The mouse has moved, with the dragged element as the event target.
dragenter
The dragged element has been moved into a drop listener, with the drop listener element as the event target.
dragover
The dragged element has been moved over a drop listener, with the drop listener element as the event target. Since the default behavior is to cancel drops, returning false or calling preventDefault() in the event handler indicates that a drop is allowed here.
dragleave
The dragged element has been moved out of a drop listener, with the drop listener element as the event target.
drop
The dragged element has been successfully dropped on a drop listener, with the drop listener element as the event target.
dragend
A drag has been ended, successfully or not, with the dragged element as the event target.

Like the mouse events of yore, listeners can be attached to elements using addEventListener() directly or by way of your favorite JS library.

Consider the following example using jQuery, also available as a live demo:

    <div id="newschool">
        <div class="dragme">Drag me!</div>
        <div class="drophere">Drop here!</div>
    </div>
 
    <script type="text/javascript">
        $(document).ready(function() {
            $('#newschool .dragme')
                .attr('draggable', 'true')
                .bind('dragstart', function(ev) {
                    var dt = ev.originalEvent.dataTransfer;
                    dt.setData("Text", "Dropped in zone!");
                    return true;
                })
                .bind('dragend', function(ev) {
                    return false;
                });
            $('#newschool .drophere')
                .bind('dragenter', function(ev) {
                    $(ev.target).addClass('dragover');
                    return false;
                })
                .bind('dragleave', function(ev) {
                    $(ev.target).removeClass('dragover');
                    return false;
                })
                .bind('dragover', function(ev) {
                    return false;
                })
                .bind('drop', function(ev) {
                    var dt = ev.originalEvent.dataTransfer;
                    alert(dt.getData('Text'));
                    return false;
                });
        });
    </script>

Thanks to the new events and jQuery, this example is both short and simple—but it packs in a lot of functionality, as the rest of this article will explain.

Before moving on, there are at least three things about the above code that are worth mentioning:

  • Drop targets are enabled by virtue of having listeners for drop events. But, per the HTML 5 spec, draggable elements need an attribute of draggable="true", set either in markup or in JavaScript.

    Thus, $('#newschool .dragme').attr('draggable', 'true').

  • The original DOM event (as opposed to jQuery’s event wrapper) offers a property called dataTransfer. Beyond just manipulating elements, the new drag and drop events accomodate the transmission of user-definable data during the course of the interaction.
  • Since these are first-class events, you can apply the technique of Event Delegation.

    What’s that? Well, imagine you have a list of 1000 list items—as part of a deeply-nested outline document, for instance. Rather than needing to attach listeners or otherwise fiddle with all 1000 items, simply attach a listener to the parent node (eg. the <ul> element) and all events from the children will propagate up to the single parent listener. As a bonus, all new child elements added after page load will enjoy the same benefits.

    Check out this demo, and the associated JS code to see more about these events and Event Delegation.

Using dataTransfer

As mentioned in the last section, the new drag and drop events let you send data along with a dragged element. But, it’s even better than that: Your drop targets can receive data transferred by content objects dragged into the window from other browser windows, and even other applications.

Since the example is a bit longer, check out the live demo and associated code to get an idea of what’s possible with dataTransfer.

In a nutshell, the stars of this show are the setData() and getData() methods of the dataTransfer property exposed by the Event object.

The setData() method is typically called in the dragstart listener, loading dataTransfer up with one or more strings of content with associated recommended content types.

For illustration, here’s a quick snippet from the example code:

    var dt = ev.originalEvent.dataTransfer;
    dt.setData('text/plain', $('#logo').parent().text());
    dt.setData('text/html', $('#logo').parent().html());
    dt.setData('text/uri-list', $('#logo')[0].src);

On the other end, getData() allows you to query for content by type (eg. text/html followed by text/plain). This, in turn, allows you to decide on acceptable content types at the time of the drop event or even during dragover to offer feedback for unacceptable types during the drag.

Here’s another example from the receiving end of the example code:

    var dt = ev.originalEvent.dataTransfer;
    $('.content_url .content').text(dt.getData('text/uri-list'));
    $('.content_text .content').text(dt.getData('text/plain'));
    $('.content_html .content').html(dt.getData('text/html'));

Where dataTransfer really shines, though, is that it allows your drop targets to receive content from sources outside your defined draggable elements and even from outside the browser altogether. Firefox accepts such drags, and attempts to populate dataTransfer with appropriate content types extracted from the external object.

Thus, you could select some text in a word processor window and drop it into one of your elements, and at least expect to find it available as text/plain content.

You can also select content in another browser window, and expect to see text/html appear in your events. Check out the outline editing demo and see what happens when you try dragging various elements (eg. images, tables, and lists) and highlighted content from other windows onto the items there.

Using Drag Feedback Images

An important aspect of the drag and drop interaction is a representation of the thing being dragged. By default in Firefox, this is a “ghost” image of the dragged element itself. But, the dataTransfer property of the original Event object exposes the method setDragImage() for use in customizing this representation.

There’s a live demo of this feature, as well as associated JS code available. The gist, however, is sketched out in these code snippets:

    var dt = ev.originalEvent.dataTransfer;
 
    dt.setDragImage( $('#feedback_image h2')[0], 0, 0);
 
    dt.setDragImage( $('#logo')[0], 32, 32);
 
    var canvas = document.createElement("canvas");
    canvas.width = canvas.height = 50;
 
    var ctx = canvas.getContext("2d");
    ctx.lineWidth = 8;
    ctx.moveTo(25,0);
    ctx.lineTo(50, 50);
    ctx.lineTo(0, 50);
    ctx.lineTo(25, 0);
    ctx.stroke();
 
    dt.setDragImage(canvas, 25, 25);

You can supply a DOM node as the first parameter to setDragImage(), which includes everything from text to images to <canvas> elements. The second two parameters indicate at what left and top offset the mouse should appear in the image while dragging.

For example, since the #logo image is 64×64, the parameters in the second setDragImage() method places the mouse right in the center of the image. On the other hand, the first call positions the feedback image such that the mouse rests in the upper left corner.

Using Drop Effects

As mentioned at the start of this article, the drag and drop interaction has been used to support actions such as copying, moving, and linking. Accordingly, the HTML 5 specification accomodates these operations in the form of the effectAllowed and dropEffect properties exposed by the Event object.

For a quick fix, check out the a live demo of this feature, as well as the associated JS code.

The basic idea is that the dragstart event listener can set a value for effectAllowed like so:

    var dt = ev.originalEvent.dataTransfer;
    switch (ev.target.id) {
        case 'effectdrag0': dt.effectAllowed = 'copy'; break;
        case 'effectdrag1': dt.effectAllowed = 'move'; break;
        case 'effectdrag2': dt.effectAllowed = 'link'; break;
        case 'effectdrag3': dt.effectAllowed = 'all'; break;
        case 'effectdrag4': dt.effectAllowed = 'none'; break;
    }

The choices available for this property include the following:

none
no operation is permitted
copy
copy only
move
move only
link
link only
copyMove
copy or move only
copyLink
copy or link only
linkMove
link or move only
all
copy, move, or link

On the other end, the dragover event listener can set the value of the dropEffect property to indicate the expected effect invoked on a successful drop. If the value does not match up with effectAllowed, the drop will be considered cancelled on completion.

In the a live demo, you should be able to see that only elements with matching effects can be dropped into the appropriate drop zones. This is accomplished with code like the follwoing:

    var dt = ev.originalEvent.dataTransfer;
    switch (ev.target.id) {
        case 'effectdrop0': dt.dropEffect = 'copy'; break;
        case 'effectdrop1': dt.dropEffect = 'move'; break;
        case 'effectdrop2': dt.dropEffect = 'link'; break;
        case 'effectdrop3': dt.dropEffect = 'all'; break;
        case 'effectdrop4': dt.dropEffect = 'none'; break;
    }

Although the OS itself can provide some feedback, you can also use these properties to update your own visible feedback, both on the dragged element and on the drop zone itself.

Conclusion

The new first-class drag and drop events in HTML5 and Firefox make supporting this form of UI interaction simple, concise, and powerful in the browser. But beyond the new simplicity of these events, the ability to transfer content between applications opens brand new avenues for web-based applications and collaboration with desktop software in general.

30 comments

Comments are now closed.

  1. Pingback from HTML5 drag and drop in Firefox 3.5 – 0xDECAFBAD on July 15th, 2009 at 19:26:

    […] hey, look! It’s another blog post—and this one is cross-posted on hacks.mozilla.com. I won’t say this is the start of a renewed blogging habit, but let’s see what […]

  2. Herohtar wrote on July 15th, 2009 at 20:23:

    The examples of the new drag and drop are all rather laggy… and they don’t look as good as the “old” method — if I try to drag an object and it immediately starts out giving my mouse the “no” cursor, I’m more likely to assume I can’t drag it.

  3. l.m.orchard wrote on July 15th, 2009 at 20:54:

    @Herohtar: Really? That seems odd. Would you mind mentioning what operating system you’re using?

  4. l.m.orchard wrote on July 15th, 2009 at 21:08:

    @Herohtar: I just checked things again in Windows, and things look smooth to me. Your mileage may vary, I guess.

    Also, I do see the circle-bar feedback until the dragged element is over a valid drop target. I don’t see that on OS X, but I think it’s technically working as intended on Windows.

  5. Drazick wrote on July 16th, 2009 at 00:31:

    Can you make Dtag&Drop interactions with the local Desktop?
    Like dragging files for Upload / Download?

  6. Laurent wrote on July 16th, 2009 at 02:07:

    Feedback Images are not displayed with the new D&D !
    Should I open a ticket for this ?
    I am using: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.1pre) Gecko/20090715 Shiretoko/3.5.1pre

  7. Pingback from Drag & Drop en HTML5 sobre Firefox 3.5 | aNieto2K on July 16th, 2009 at 03:20:

    […] podemos ver en el ejemplo (de hacks.mozilla) se han  diferenciado 2 […]

  8. Pingback from Drag & Drop en HTML5 sobre Firefox 3.5 : Blogografia on July 16th, 2009 at 04:03:

    […] podemos ver en el ejemplo (de hacks.mozilla) se han  diferenciado 2 […]

  9. David L. wrote on July 16th, 2009 at 05:03:

    Great post. In the Drag Effect example, the “Drag 2 (link)” is not permitted to drop onto the “Drop 2 (link)” target, even though the HTML attributes and script are applied consistent with its siblings (copy, move, all, none). Am I missing something? (I’m using 3.5 on Windows XP)

  10. Neil Deakin wrote on July 16th, 2009 at 05:22:

    As the implementor of this drag and drop feature in Mozilla, I am most displeased that a highly visible article about it on mozilla.org would be written using jQuery rather than demonstrating the correct way to use it.

  11. Richie wrote on July 16th, 2009 at 06:26:

    Drag and drop in Firefox 3.1 allowed me to drag tabs to the desktop or into a folder and get links. Now when I drag tabs I see an image of the web page, but no link ever appears when I release the mouse button.

    Also I was able to drag tabs into Gmail messages in 3.1 and get a nicely formatted link showing the bookmark/page title and containing an embedded url, now I only get an unformatted plain-text url.

    Is this related to the new HTML5?

  12. Herohtar wrote on July 16th, 2009 at 07:18:

    Interesting, I tested it again today and it didn’t have any problems. I’m not sure what was making the new method lag before.

    After playing with it some more I think the cursor feedback makes sense, as it’s more in line with how image dragging works.

  13. l.m.orchard wrote on July 16th, 2009 at 07:23:

    @Drazick: Alas, no drag-and-drop file uploads so far, neither in the HTML 5 spec nor in Firefox. I think there are some issues to work out with the concept, but I hope it’s eventually possible

    @Herohtar: Yeah, I think that cursor feedback is the same as what you’d find in Windows Explorer, too. I think the thing is that since drag and drop in the browser up until now has been generally non-standard with respect to the OS, the standard OS behavior looks wrong in a browser.

  14. Remy Sharp wrote on July 16th, 2009 at 10:59:

    Great tutorial, and particularly good use of jQuery – just slightly a pain that you have to dip in to the “originalEvent” to get the dataTransfer out – but since it’s a custom object, it’s the only way for now.

    I wrote up HTML 5 native drag and drop myself on the HTML 5 Doctor site last week, which also covers custom drag images – but also how to get this working in “other” browsers too. Thought it might be useful to your readers.

  15. Christopher Blizzard wrote on July 17th, 2009 at 05:02:

    @Neil – really? Lots of people use jQuery. I’m fine with using libraries from time to time and even using them in examples. Les wrote the article and it was his call but I never gave it a thought.

  16. l.m.orchard wrote on July 17th, 2009 at 05:59:

    @Neil: I’m sorry you’re displeased. What would you consider the correct way to use the drag & drop feature? Am I using it improperly with jQuery – if so, how could the examples be corrected?

    Or, is the use of jQuery the thing to which you object? Because, if that’s it, I’ll say that jQuery is one of the most popular JS frameworks – and thus will be the way many web developers approach the new drag & drop events.

    We’ve got full docs on the events at developer.mozilla.org. Since this article is at hacks.mozilla.org, it was intended to be a practical introduction to the new events.

    Apropos of that, jQuery allowed this to be done with concise and (hopefully) readable code that might be directly useful to webdevs. Users of other frameworks (or none at all) should be able to port the examples accordingly.

  17. Pingback from HTML 5 Weekly Review #3 | Jeff Siarto on July 17th, 2009 at 10:00:

    […] 5 Drag and Drop A write‐​up on the fea­tures and usage of HTML 5 drag and drop in Fire­fox […]

  18. l.m.orchard wrote on July 17th, 2009 at 13:12:

    @David L.: Strange – I can’t reproduce the drop effect issue, neither on my Mac nor with Windows in a VM.

  19. Laurent wrote on July 17th, 2009 at 16:09:

    I totally agree with Neil, the use of jQuery is a total mistake. And this is NOT a readable code, jQuery is NOT readable at all, it is just mess and mistakes everywhere. Using jQuery makes this example totally miss its point. This is NOT a practical introduction to the new event, it is just confusing because of the crappy jQuery.

  20. Inflecto Systems [Web Based Applications] wrote on July 18th, 2009 at 05:48:

    Nice article. We have been using JQuery based drag and drop for quite a bit but this can be pretty flakey sometimes. Some of the stuff in HTML 5 and CSS 3 is really great.

    We have just got to run a campaign to kill off some of the older browsers on the net now!

  21. l.m.orchard wrote on July 18th, 2009 at 07:16:

    @Laurent: Thank you for the kind comments. How would you fix this article to correct the mess and mistakes everywhere?

  22. Pingback from HTML5 drag and drop in Firefox 3.5 | En Blog Alta on August 2nd, 2009 at 20:26:

    […] hey, look! It’s another blog post—and this one is cross-posted on hacks.mozilla.com. I won’t say this is the start of a renewed blogging habit, but let’s see what […]

  23. HB wrote on August 18th, 2009 at 12:09:

    While I agree that jQuery is a lot easier to write than it is to read, I don’t think the jQuery code in this example is that difficult to absorb. It’s not my library of choice but it’s pretty obvious what’s going on.

    The meat and potatoes appears to be plain JavaScript anyway, the only things replaced by jQuery are attaching events and the ubiquitous selection tool.

    Thanks for this post!

  24. WebManWalking wrote on November 7th, 2009 at 02:21:

    @Neil Deakin: Correct me if I’m mistaken, but doesn’t John Resig work for mozilla.org too? If so, “why so serious”?

    There’s a real problem with consistent ghosting (at least, of text) as the default drag image on Windows Firefox (even now, months later, in 3.5.5). Just over the cubicle partition where I work, my coworker Melvin is getting text draggables ghosted, but I’m not, even though I wrote the code. Is it because I have Firebug? Maybe the Windows Firefox developers could ask Mac Firefox developers how they did it?

    @I.m.orchard: I was able to get your jQuery code working on every major Mac/PC platform except Opera 10. I had to removeClass in the drop handler in addition to the dragleave handler because some browsers don’t fire the dragleave event if you drop. (That is, if you don’t removeClass there too, you get a persistent highlighting of the droppable in those browsers.)

    Your code here is much lighter weight than including all of jquery.ui just for its drag-and-drop support. Thanks a bunch (“Fargo”).

  25. Matthew Holloway wrote on December 16th, 2009 at 18:15:

    Does this also work if you’ve got an image in your clipboard and you paste it?

  26. Kalle wrote on January 12th, 2010 at 08:56:

    The mistake with using jQuery is that not everybody knows jQuery (like myself). I come to this example by hopping from the Mozilla page on new features in FF 3.5, and I am now forced to go look into jQuery just to figure out how the example works. I don’t mind libs, but when I’m testing new features I prefer to test them in plain javascript first. I still don’t have a working example in JS, but I would have had one if the example here had been in plain JS.

  27. bit wrote on May 22nd, 2010 at 14:49:

    And another thing… why use a class and append “draggable=true” to it when you can just markup elements with draggable=”true” and loop through them?

    1. Marcio wrote on July 16th, 2010 at 20:01:

      Simple… it’s useful when you don’t want draggable elements in your page while JavaScript is disabled.
      Users will not be able to drag things around in case the page is not fully functional and you will be totally shure that only a graceful degraded version of the application is going to be available.

      ;)

      Great post by the way. Can’t see why some readers think the code is “NOT readable at all”… so go there and waste your time typing 4.000 lines of code and we will see what’s really readable =D

  28. Anil Namde wrote on February 3rd, 2011 at 06:15:

    We generally use the feature detection in the JavaScript to see if something is supported or not. But its not working nicely across the browser can you please check the question asked below on stackoverflow mentioned below?
    http://stackoverflow.com/questions/3828183/html5-datatransfer-detection-error-in-chrome

    // works on Firefox however now working for IE, Safari and Chrome
    if (“files” in DataTransfer.prototype) {
    alert(“supported”);
    }

  29. Pingback from BeyondHTML5 | Blog | Drag and drop fun on May 22nd, 2011 at 20:08:

    […] thats really it. Simple, huh? Check out an example (code, demo)For more information, see:http://hacks.mozilla.org/2009/07/html5-drag-and-drop/http://dev.w3.org/html5/spec-author-view/dnd.html#the-datatransfer-interface Tweet Posted in HTML5 […]

Comments are closed for this article.