IE4 最早在网页中为 JavaScript 引入了对拖放功能的支持。当时,网页中只有两样东西可以触发拖 21 放:图片和文本。拖动图片就是简单地在图片上按住鼠标不放然后移动鼠标。而对于文本,必须先选中, 然后再以同样的方式拖动。在 IE4 中,唯一有效的放置目标是文本框。IE5 扩展了拖放能力,添加了新 的事件,让网页中几乎一切都可以成为放置目标。IE5.5 又进一步,允许几乎一切都可以拖动(IE6 也支 持这个功能)。HTML5 在 IE 的拖放实现基础上标准化了拖放功能。所有主流浏览器都根据 HTML5 规范 实现了原生的拖放。 关于拖放最有意思的可能就是可以跨窗格、跨浏览器容器,有时候甚至可以跨应用程序拖动元素。 浏览器对拖放的支持可以让我们实现这些功能。

(1) dragstart (2) drag (3) dragend 26 在按住鼠标键不放并开始移动鼠标的那一刻,被拖动元素上会触发 dragstart 事件。此时光标会 变成非放置符号(圆环中间一条斜杠),表示元素不能放到自身上。拖动开始时,可以在 ondragstart 事件处理程序中通过 JavaScript 执行某些操作。 dragstart 事件触发后,只要目标还被拖动就会持续触发 drag 事件。这个事件类似于 mousemove, 即随着鼠标移动而不断触发。当拖动停止时(把元素放到有效或无效的放置目标上),会触发 dragend 事件。

拖放事件

拖放事件几乎可以让开发者控制拖放操作的方方面面。关键的部分是确定每个事件是在哪里触发 的。有的事件在被拖放元素上触发,有的事件则在放置目标上触发。在某个元素被拖动时,会(按顺序) 触发以下事件: 所有这 3 个事件的目标都是被拖动的元素。默认情况下,浏览器在拖动开始后不会改变被拖动元素 的外观,因此是否改变外观由你来决定。不过,大多数浏览器此时会创建元素的一个半透明副本,始终 跟随在光标下方。 在把元素拖动到一个有效的放置目标上时,会依次触发以下事件: (1) dragenter (2) dragover (3) dragleave 或 drop 只要一把元素拖动到放置目标上,dragenter 事件(类似于 mouseover 事件)就会触发。dragenter 事件触发之后,会立即触发 dragover 事件,并且元素在放置目标范围内被拖动期间此事件会持续触发。 当元素被拖动到放置目标之外,dragover 事件停止触发,dragleave 事件触发(类似于 mouseout 事件)。如果被拖动元素被放到了目标上,则会触发 drop 事件而不是 dragleave 事件。这些事件的目 标是放置目标元素。

自定义放置目标

在把某个元素拖动到无效放置目标上时,会看到一个特殊光标(圆环中间一条斜杠)表示不能放下。 即使所有元素都支持放置目标事件,这些元素默认也是不允许放置的。如果把元素拖动到不允许放置的 目标上,无论用户动作是什么都不会触发 drop 事件。不过,通过覆盖 dragenter 和 dragover 事件 的默认行为,可以把任何元素转换为有效的放置目标。例如,如果有一个 ID 为"droptarget"的

let droptarget = document.getElementById("droptarget");
    droptarget.addEventListener("dragover", (event) => {
      event.preventDefault();
});
    droptarget.addEventListener("dragenter", (event) => {
      event.preventDefault();
});

执行上面的代码之后,把元素拖动到这个

上应该可以看到光标变成了允许放置的样子。另外, drop 事件也会触发。 在 Firefox 中,放置事件的默认行为是导航到放在放置目标上的 URL。这意味着把图片拖动到放置 目标上会导致页面导航到图片文件,把文本拖动到放置目标上会导致无效 URL 错误。为阻止这个行为, 在 Firefox 中必须取消 drop 事件的默认行为:

droptarget.addEventListener("drop", (event) => {
      event.preventDefault();
});

dataTransfer对象

除非数据受影响,否则简单的拖放并没有实际意义。为实现拖动操作中的数据传输,IE5 在 event 对象上暴露了 dataTransfer 对象,用于从被拖动元素向放置目标传递字符串数据。因为这个对象是 event 的属性,所以在拖放事件的事件处理程序外部无法访问 dataTransfer。在事件处理程序内部,可以使用这个对象的属性和方法实现拖放功能。dataTransfer 对象现在已经纳入了 HTML5 工作草案。 15 dataTransfer 对象有两个主要方法:getData()和 setData()。顾名思义,getData()用于获 取 setData()存储的值。setData()的第一个参数以及 getData()的唯一参数是一个字符串,表示要 设置的数据类型:"text"或"URL",如下所示:

// 传递文本
event.dataTransfer.setData("text", "some text"); let text = event.dataTransfer.getData("text");
// 传递URL
event.dataTransfer.setData("URL", "http://www.wrox.com/"); let url = event.dataTransfer.getData("URL");

虽然这两种数据类型是 IE 最初引入的,但 HTML5 已经将其扩展为允许任何 MIME 类型。为向后 兼容,HTML5 还会继续支持"text"和"URL",但它们会分别被映射到"text/plain"和"text/uri-list"。 dataTransfer 对象实际上可以包含每种 MIME 类型的一个值,也就是说可以同时保存文本和 URL,两者不会相互覆盖。存储在 dataTransfer 对象中的数据只能在放置事件中读取。如果没有在 ondrop 事件处理程序中取得这些数据,dataTransfer 对象就会被销毁,数据也会丢失。 在从文本框拖动文本时,浏览器会调用 setData()并将拖动的文本以"text"格式存储起来。类似 地,在拖动链接或图片时,浏览器会调用 setData()并把 URL 存储起来。当数据被放置在目标上时, 可以使用 getData()获取这些数据。当然,可以在 dragstart 事件中手动调用 setData()存储自定 义数据,以便将来使用。 作为文本的数据和作为 URL 的数据有一个区别。当把数据作为文本存储时,数据不会被特殊对待。 而当把数据作为 URL 存储时,数据会被作为网页中的一个链接,意味着如果把它放到另一个浏览器窗 口,浏览器会导航到该 URL。

原生拖放

直到版本 5,Firefox 都不能正确地把"url"映射为"text/uri-list"或把"text"映射为"text/plain"。 不过,它可以把"Text"(第一个字母大写)正确映射为"text/plain"。在通过 dataTransfer 获取 数据时,为保持最大兼容性,需要对 URL 检测两个值并对文本使用"Text":

// 读取文本 24
let dataTransfer = event.dataTransfer;
// 读取URL
let url = dataTransfer.getData("url") || dataTransfer.getData("text/uri-list");
     let text = dataTransfer.getData("Text");

这里要注意,首先应该尝试短数据名。这是因为直到版本 10,IE 都不支持扩展的类型名,而且会 在遇到无法识别的类型名时抛出错误。