Firefox 3.5 中的 HTML5 拖放

这篇文章来自 Les Orchard,他曾在 Mozilla 的网页开发团队工作。

简介

拖放是图形用户界面中最基本的操作之一。通过一个动作,它允许用户将对象的选中与操作的执行配对,通常包括操作中的第二个对象。这是一个简单但强大的 UI 概念,用于支持复制、列表重新排序、删除(如垃圾箱/回收站),甚至创建链接关系。

由于它是如此基本,因此从浏览器首次在 DHTML 中提供鼠标事件起,在 Web 应用程序中提供拖放就是理所当然的了。但是,尽管mousedownmousemovemouseup 使其成为可能,但实现仍然局限于浏览器窗口的范围。此外,由于这些事件只涉及被拖动的对象,因此在交互完成时找到放置的目标是一个挑战。

当然,这并不能阻止大多数现代 JavaScript 框架抽象掉大部分问题,并在他们做这件事的时候添加一些修饰。但是,如果浏览器提供对拖放的一流支持,甚至将其扩展到窗口沙箱之外,岂不是很好吗?

事实证明,HTML 5 规范关于新的拖放事件的章节正是对这个愿望的回应,而Firefox 3.5 包含了这些事件的实现

如果你想直接跳转到代码,我已经整理了一些简单的演示,演示了这些新事件。

我甚至满足了自己的一个愿望,构建了一个大纲编辑器的雏形,其中每个可拖动的元素也是一个放置目标——在一个复杂的文档中可能会有几十到几百个这样的目标,这在我过去试图用普通的鼠标事件来解决这个问题时,给我带来了一些小困扰。

而且,所有这些都可以从我专门为这篇文章创建的GitHub 仓库中下载或克隆。

新的拖放事件

所以,话不多说,以下是新的拖放事件,按照你可能期望它们被触发的顺序排列

dragstart
已开始拖动,被拖动的元素是事件目标。
drag
鼠标已移动,被拖动的元素是事件目标。
dragenter
被拖动的元素已移入放置监听器,放置监听器元素是事件目标。
dragover
被拖动的元素已移到放置监听器上,放置监听器元素是事件目标。由于默认行为是取消放置,因此在事件处理程序中返回false或调用preventDefault()表示此处允许放置。
dragleave
被拖动的元素已移出放置监听器,放置监听器元素是事件目标。
drop
被拖动的元素已成功放置在放置监听器上,放置监听器元素是事件目标。
dragend
拖动已结束,无论成功与否,被拖动的元素是事件目标。

就像过去的鼠标事件一样,可以使用addEventListener()直接或通过你最喜欢的 JS 库将监听器附加到元素。

考虑以下使用 jQuery 的示例,也可以作为实时演示

    <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>

由于有了新事件和 jQuery,这个示例既短又简单——但它包含了许多功能,正如本文的其余部分将解释的那样。

在继续之前,关于上面的代码,至少有以下三点值得一提

  • 放置目标通过具有放置事件的监听器来启用。但是,根据 HTML 5 规范,可拖动的元素需要一个draggable="true"属性,该属性可以在标记或 JavaScript 中设置。

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

  • 原始 DOM 事件(与 jQuery 的事件包装器相反)提供了一个名为dataTransfer的属性。除了操作元素之外,新的拖放事件还允许在交互过程中传输用户定义的数据。
  • 由于这些是一流的事件,你可以应用事件委托技术

    那是什么?想象一下,你有一个包含 1000 个列表项的列表——例如,作为深度嵌套的大纲文档的一部分。与其需要附加监听器或以其他方式处理所有 1000 个项目,不如简单地将监听器附加到父节点(例如,<ul> 元素),所有来自子节点的事件都会传播到单个父监听器。作为一项额外的好处,在页面加载后添加的所有新子元素都将享有相同的优势。

    查看此演示,以及相关的 JS 代码,以了解有关这些事件和事件委托的更多信息。

使用 dataTransfer

如上一节所述,新的拖放事件允许你将数据与被拖动的元素一起发送。但它比这更好:你的放置目标可以接收从其他浏览器窗口甚至*其他应用程序*中拖入窗口的内容对象传输的数据。

由于示例有点长,查看实时演示相关代码,以了解使用dataTransfer可能实现的功能。

简而言之,这个节目的主角是事件对象公开的dataTransfer属性的setData()getData()方法。

setData()方法通常在dragstart监听器中调用,使用与推荐的 content type相关联的一个或多个内容字符串来加载dataTransfer

为了说明,以下是示例代码中的一个快速片段

    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);

另一方面,getData()允许你按类型查询内容(例如,text/html,然后是text/plain)。这反过来让你能够在drop事件甚至dragover期间决定可接受的内容类型,以便在拖动期间对不可接受的类型提供反馈。

以下是来自示例代码接收端的另一个示例

    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'));

然而,dataTransfer真正闪耀的地方在于它允许你的放置目标接收来自你定义的可拖动元素之外,甚至来自浏览器本身之外的源的内容。Firefox 接受此类拖动,并尝试使用从外部对象提取的适当 content type 来填充dataTransfer

因此,你可以在文字处理器窗口中选择一些文本,并将其拖放到你的其中一个元素中,并且至少期望找到它作为text/plain内容可用。

你也可以在另一个浏览器窗口中选择内容,并期望看到text/html出现在你的事件中。查看大纲编辑演示,看看当你尝试将其他窗口中的各种元素(例如,图像、表格和列表)和突出显示的内容拖放到其中的项目时会发生什么。

使用拖动反馈图像

拖放交互的一个重要方面是对被拖动对象的表示。在 Firefox 中,默认情况下,这是一个被拖动元素本身的“幻影”图像。但是,事件对象的原始dataTransfer属性公开了setDragImage()方法,用于自定义此表示形式。

这里有一个实时演示,展示了此功能,以及相关的 JS 代码。但是,其要点在这些代码片段中概述

    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);

你可以提供一个 DOM 节点作为setDragImage()的第一个参数,其中包括从文本到图像到<canvas>元素的所有内容。后两个参数指示在拖动时鼠标应该出现在图像中的哪个左偏移量和上偏移量。

例如,由于#logo 图像为 64×64,第二个setDragImage()方法中的参数将鼠标放置在图像的中心。另一方面,第一个调用将反馈图像定位,使得鼠标位于左上角。

使用放置效果

如本文开头所述,拖放交互已用于支持复制、移动和链接等操作。因此,HTML 5 规范在事件对象公开的effectAlloweddropEffect属性的形式下适应了这些操作。

为了快速解决,查看实时演示,展示了此功能,以及相关的 JS 代码.

基本思想是,dragstart事件监听器可以像这样为effectAllowed设置一个值

    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;
    }

此属性可用的选项包括以下内容

none
不允许进行任何操作
copy
仅复制
move
仅移动
link
仅链接
copyMove
仅复制或移动
copyLink
仅复制或链接
linkMove
仅链接或移动
all
复制、移动或链接

另一方面,dragover 事件监听器可以设置 dropEffect 属性的值,以指示在成功放下时预期的效果。如果该值与 effectAllowed 不匹配,则放下将在完成时被视为取消。

实时演示 中,您应该能够看到只有具有匹配效果的元素才能被放到相应的放置区域中。这是通过以下代码完成的

    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;
    }

尽管操作系统本身可以提供一些反馈,但您也可以使用这些属性来更新您自己的可见反馈,无论是在拖动的元素上还是在放置区域本身。

结论

HTML5 和 Firefox 中新的第一类拖放事件使在浏览器中支持这种形式的 UI 交互变得简单、简洁和强大。但除了这些事件的新简便性之外,能够在应用程序之间传输内容为基于 Web 的应用程序开辟了全新的途径,并与一般的桌面软件协作。


30 条评论

  1. […] 嘿,看!这是另一篇博客文章——这篇文章是在 hacks.mozilla.com 上交叉发布的。我不会说这是重新开始博客习惯的开始,但让我们看看 […]

    2009 年 7 月 15 日 下午 7:26

  2. Herohtar

    新的拖放示例都相当滞后……而且它们看起来不如“旧”方法好——如果我尝试拖动一个对象,并且它立即开始给我鼠标“禁止”光标,我更有可能认为我不能拖动它。

    2009 年 7 月 15 日 下午 8:23

  3. l.m.orchard

    @Herohtar:真的吗?这似乎很奇怪。您是否介意提及您正在使用的操作系统?

    2009 年 7 月 15 日 下午 8:54

  4. l.m.orchard

    @Herohtar:我刚刚在 Windows 中再次检查了,对我来说看起来很流畅。您的里程数可能会有所不同,我想。

    此外,在拖动的元素位于有效的放置目标之上之前,我确实看到了圆形条反馈。我在 OS X 上没有看到,但我认为它在 Windows 上按照预期工作。

    2009 年 7 月 15 日 下午 9:08

  5. Drazick

    您可以进行 Dtag&Drop 与本地桌面的交互吗?
    就像拖动文件进行上传/下载?

    2009 年 7 月 16 日 上午 12:31

  6. Laurent

    反馈图像没有与新的 D&D 一起显示!
    我应该为此创建一个工单吗?
    我使用的是:Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.1pre) Gecko/20090715 Shiretoko/3.5.1pre

    2009 年 7 月 16 日 上午 2:07

  7. […] 我们可以在示例(来自 hacks.mozilla)中看到,它们已区分 2 […]

    2009 年 7 月 16 日 上午 3:20

  8. […] 我们可以在示例(来自 hacks.mozilla)中看到,它们已区分 2 […]

    2009 年 7 月 16 日 上午 4:03

  9. David L.

    很棒的帖子。在拖放效果示例中,“拖动 2(链接)”不允许放到“放置 2(链接)”目标中,即使 HTML 属性和脚本与它的兄弟节点(复制、移动、全部、无)一致。我错过了什么吗?(我使用的是 Windows XP 上的 3.5)

    2009 年 7 月 16 日 上午 5:03

  10. Neil Deakin

    作为 Mozilla 中此拖放功能的实施者,我非常不满 mozilla.org 上一篇关于它的高度可见的文章使用 jQuery 而不是演示使用它的正确方法。

    2009 年 7 月 16 日 上午 5:22

  11. Richie

    Firefox 3.1 中的拖放功能允许我将标签拖动到桌面或文件夹中并获取链接。现在,当我拖动标签时,我会看到网页的图像,但当我释放鼠标按钮时,永远不会出现链接。

    此外,我能够在 3.1 中将标签拖动到 Gmail 消息中并获得一个格式良好的链接,显示书签/页面标题并包含嵌入的 URL,现在我只获得一个未格式化的纯文本 URL。

    这与新的 HTML5 有关吗?

    2009 年 7 月 16 日 上午 6:26

  12. Herohtar

    有趣的是,我今天再次测试了它,它没有任何问题。我不确定是什么让新方法以前滞后。

    在进一步玩弄它之后,我认为光标反馈是有道理的,因为它更符合图像拖动的工作方式。

    2009 年 7 月 16 日 上午 7:18

  13. l.m.orchard

    @Drazick:唉,到目前为止,既没有 HTML 5 规范,也没有 Firefox 中的拖放文件上传。我认为在概念上有一些问题需要解决,但我希望它最终成为可能

    @Herohtar:是的,我认为光标反馈与您在 Windows 资源管理器中找到的反馈相同。我认为问题在于,由于到目前为止浏览器中的拖放功能相对于操作系统来说通常是非标准的,因此标准操作系统的行为在浏览器中看起来是错误的。

    2009 年 7 月 16 日 上午 7:23

  14. Remy Sharp

    很棒的教程,特别是 jQuery 的使用——只是稍微有点痛苦,您必须进入“originalEvent”才能获得 dataTransfer——但由于它是一个自定义对象,所以现在是唯一的方法。

    我上周自己写了 HTML 5 原生拖放,并在 HTML 5 Doctor 网站上发布,它还涵盖了自定义拖动图像——以及如何在“其他”浏览器中实现此功能。我认为这对您的读者可能会有用。

    2009 年 7 月 16 日 上午 10:59

  15. Christopher Blizzard

    @Neil——真的吗?很多人都使用 jQuery。我不介意偶尔使用库,甚至在示例中使用它们。Les 写了这篇文章,这是他的决定,但我从未想过。

    2009 年 7 月 17 日 上午 5:02

  16. l.m.orchard

    @Neil:很抱歉让你不高兴。您认为使用拖放功能的正确方法是什么?我是否不正确地使用 jQuery——如果是这样,该如何更正这些示例?

    或者,您反对的是 jQuery 的使用吗?因为,如果是这样的话,我会说 jQuery 是最流行的 JS 框架之一——因此将是许多 Web 开发人员使用新拖放事件的方式。

    我们在 developer.mozilla.org 上有关于这些事件的完整文档。由于这篇文章在 hacks.mozilla.org 上,因此旨在作为对新事件的实用介绍。

    关于这一点,jQuery 允许使用简洁且(希望)可读的代码来完成此操作,这些代码可能对 Web 开发人员直接有用。其他框架(或根本没有框架)的用户应该能够相应地移植这些示例。

    2009 年 7 月 17 日 上午 5:59

  17. […] 5 拖放 关于 Firefox 中 HTML 5 拖放功能及其用法的说明 […]

    2009 年 7 月 17 日 上午 10:00

  18. l.m.orchard

    @David L.:奇怪——我无法重现放置效果问题,无论是在我的 Mac 上还是在 Windows 虚拟机中。

    2009 年 7 月 17 日 下午 1:12

  19. Laurent

    我完全同意 Neil 的观点,使用 jQuery 绝对是一个错误。而且这并非可读代码,jQuery 根本不可读,到处都是混乱和错误。使用 jQuery 使此示例完全失去了意义。这不是对新事件的实用介绍,因为它因为糟糕的 jQuery 而令人困惑。

    2009 年 7 月 17 日 下午 4:09

  20. Inflecto Systems [基于 Web 的应用程序]

    不错的文章。我们一直在使用基于 JQuery 的拖放功能,但有时它可能非常不可靠。HTML 5 和 CSS 3 中的一些内容非常棒。

    我们现在必须开展一项活动,淘汰网络上的一些旧浏览器!

    2009 年 7 月 18 日 上午 5:48

  21. l.m.orchard

    @Laurent:感谢您的友好评论。您将如何修复这篇文章以纠正所有混乱和错误?

    2009 年 7 月 18 日 上午 7:16

  22. […] 嘿,看!这是另一篇博客文章——这篇文章是在 hacks.mozilla.com 上交叉发布的。我不会说这是重新开始博客习惯的开始,但让我们看看 […]

    2009 年 8 月 2 日 下午 8:26

  23. HB

    虽然我同意 jQuery 比阅读更容易编写,但我认为本示例中的 jQuery 代码并不难理解。它不是我选择的库,但它很明显发生了什么。

    核心内容似乎是纯 JavaScript,jQuery 替换的只是附加事件和无处不在的选择工具。

    感谢您发布这篇文章!

    2009 年 8 月 18 日 下午 12:09

  24. WebManWalking

    @Neil Deakin:如果我理解错误,请纠正我,但 John Resig 也在 mozilla.org 工作吗?如果是这样,“为什么那么严肃”?

    在 Windows Firefox 上,默认的拖动图像(至少对于文本来说)存在着一致的幽灵问题(即使是现在,几个月后,在 3.5.5 中也是如此)。在我工作的地方,隔着隔间,我的同事 Melvin 的文本可拖动元素会产生幽灵,但我不会,即使是我编写了代码。是因为我有 Firebug 吗?也许 Windows Firefox 开发人员可以询问 Mac Firefox 开发人员他们是怎么做到的?

    @I.m.orchard:我能够在我的所有主要 Mac/PC 平台上让您的 jQuery 代码正常运行,除了 Opera 10。我必须在放下处理程序中删除类,以及在拖离处理程序中,因为一些浏览器不会在您放下时触发拖离事件。(也就是说,如果您不也在那里删除类,那么在这些浏览器中,您将获得可放置元素的持久突出显示。)

    您这里的代码比仅仅为了它的拖放支持而包含整个 jquery.ui 轻量级得多。非常感谢(“Fargo”)。

    2009 年 11 月 7 日 上午 2:21

  25. Matthew Holloway

    如果您剪贴板中有图像并将其粘贴,这也能起作用吗?

    2009 年 12 月 16 日 下午 6:15

  26. Kalle

    使用 jQuery 的错误在于,并非每个人都了解 jQuery(就像我一样)。我从 Mozilla 页面上的 FF 3.5 新功能中跳转到这个示例,现在我被迫去了解 jQuery 才能弄清楚这个示例是如何工作的。我不介意库,但在测试新功能时,我更喜欢首先用纯 JavaScript 测试它们。我仍然没有用 JS 制作出有效的示例,但如果这里的示例是用纯 JS 编写的,我本来可以做到的。

    2010 年 1 月 12 日 上午 8:56

  27. bit

    还有一件事……当您可以直接使用 draggable=”true” 标记元素并遍历它们时,为什么要使用一个类并向它追加 “draggable=true” 呢?

    2010 年 5 月 22 日 下午 2:49

    1. Marcio

      很简单……当您不希望页面中的可拖动元素在 JavaScript 被禁用时,这很有用。
      如果页面无法完全正常工作,用户将无法拖动东西,您可以完全确定,只有应用程序的优雅降级版本可用。

      ;)

      顺便说一下,很棒的帖子。我不明白为什么有些读者认为代码“根本不可读”……所以去那里浪费你的时间,输入 4,000 行代码,我们看看什么才是真正的可读 =D

      2010 年 7 月 16 日 下午 8:01

  28. Anil Namde

    我们通常在 JavaScript 中使用特征检测来查看某个功能是否受支持。但是它在不同浏览器之间无法正常工作,你能否查看一下下面提到的 Stack Overflow 上的问题?
    http://stackoverflow.com/questions/3828183/html5-datatransfer-detection-error-in-chrome

    // 在 Firefox 上有效,但在 IE、Safari 和 Chrome 上无效
    if (“files” in DataTransfer.prototype) {
    alert(“supported”);
    }

    2011 年 2 月 3 日 下午 6:15

  29. […] 仅此而已。很简单,对吧?查看示例(代码,演示)有关更多信息,请参见:https://hacks.mozilla.ac.cn/2009/07/html5-drag-and-drop/http://dev.w3.org/html5/spec-author-view/dnd.html#the-datatransfer-interface 推文 发布在 HTML5 […]

    2011 年 5 月 22 日 下午 8:08

本文的评论已关闭。