16.2 原生拖放

优质
小牛编辑
111浏览
2023-12-01

最早在网页中引入JavaScript 拖放功能的是IE4。当时,网页中只有两种对象可以拖放:图像和某些文本。拖动图像时,把鼠标放在图像上,按住鼠标不放就可以拖动它。拖动文本时,要先选中文本,然后可以像拖动图像一样拖动被选中的文本。在IE 4 中,唯一有效的放置目标是文本框。到了IE5,拖放功能得到扩展,添加了新的事件,而且几乎网页中的任何元素都可以作为放置目标。IE5.5 更进一步,让网页中的任何元素都可以拖放。(IE6 同样也支持这些功能。)HTML5 以IE 的实例为基础制定了拖放规范。Firefox 3.5、Safari 3+和Chrome 也根据HTML5 规范实现了原生拖放功能。

说到拖放,最有意思的恐怕就是能够在框架间、窗口间,甚至在应用间拖放网页元素了。浏览器对拖放的支持为实现这些功能提供了便利。

16.2.1 拖放事件

通过拖放事件,可以控制拖放相关的各个方面。其中最关键的地方在于确定哪里发生了拖放事件,有些事件是在被拖动的元素上触发的,而有些事件是在放置目标上触发的。拖动某元素时,将依次触发下列事件:
  • (1) dragstart
  • (2) drag
  • (3) dragend

按下鼠标键并开始移动鼠标时,会在被拖放的元素上触发dragstart 事件。此时光标变成“不能放”符号(圆环中有一条反斜线),表示不能把元素放到自己上面。拖动开始时,可以通过ondragstart事件处理程序来运行JavaScript 代码。

触发dragstart 事件后,随即会触发drag 事件,而且在元素被拖动期间会持续触发该事件。这个事件与mousemove 事件相似,在鼠标移动过程中,mousemove 事件也会持续发生。当拖动停止时(无论是把元素放到了有效的放置目标,还是放到了无效的放置目标上),会触发dragend 事件。

上述三个事件的目标都是被拖动的元素。默认情况下,浏览器不会在拖动期间改变被拖动元素的外观,但你可以自己修改。不过,大多数浏览器会为正被拖动的元素创建一个半透明的副本,这个副本始终跟随着光标移动。

当某个元素被拖动到一个有效的放置目标上时,下列事件会依次发生:
  • (1) dragenter
  • (2) dragover
  • (3) dragleave 或drop

只要有元素被拖动到放置目标上,就会触发dragenter 事件(类似于mouseover 事件)。紧随其后的是dragover 事件,而且在被拖动的元素还在放置目标的范围内移动时,就会持续触发该事件。如果元素被拖出了放置目标,dragover 事件不再发生,但会触发dragleave 事件(类似于mouseout事件)。如果元素被放到了放置目标中,则会触发drop 事件而不是dragleave 事件。上述三个事件的目标都是作为放置目标的元素。

16.2.2 自定义放置目标

在拖动元素经过某些无效放置目标时,可以看到一种特殊的光标(圆环中有一条反斜线),表示不能放置。虽然所有元素都支持放置目标事件,但这些元素默认是不允许放置的。如果拖动元素经过不允许放置的元素,无论用户如何操作,都不会发生drop 事件。不过,你可以把任何元素变成有效的放置目标,方法是重写dragenter 和dragover 事件的默认行为。例如,假设有一个ID 为"droptarget"的<div>元素,可以用如下代码将它变成一个放置目标。

var droptarget = document.getElementById("droptarget");
EventUtil.addHandler(droptarget, "dragover",
function(event) {
EventUtil.preventDefault(event);
});
EventUtil.addHandler(droptarget, "dragenter",
function(event) {
EventUtil.preventDefault(event);
});

以上代码执行后,你就会发现当拖动着元素移动到放置目标上时,光标变成了允许放置的符号。当然,释放鼠标也会触发drop 事件。

在Firefox 3.5+中,放置事件的默认行为是打开被放到放置目标上的URL。换句话说,如果是把图像拖放到放置目标上,页面就会转向图像文件;而如果是把文本拖放到放置目标上,则会导致无效URL错误。因此,为了让Firefox 支持正常的拖放,还要取消drop 事件的默认行为,阻止它打开URL:

EventUtil.addHandler(droptarget, "drop", function(event){
    EventUtil.preventDefault(event);
});

16.2.3 dataTransfer对象

只有简单的拖放而没有数据变化是没有什么用的。为了在拖放操作时实现数据交换,IE 5 引入了dataTransfer 对象,它是事件对象的一个属性,用于从被拖动元素向放置目标传递字符串格式的数据。

因为它是事件对象的属性,所以只能在拖放事件的事件处理程序中访问dataTransfer 对象。在事件处理程序中,可以使用这个对象的属性和方法来完善拖放功能。目前,HTML5 规范草案也收入了dataTransfer 对象。

dataTransfer 对象有两个主要方法:getData()和setData()。不难想象,getData()可以取得由setData()保存的值。setData()方法的第一个参数,也是getData()方法唯一的一个参数,是一个字符串,表示保存的数据类型,取值为"text"或"URL",如下所示:

//设置和接收文本数据
event.dataTransfer.setData("text", "some text");
var text = event.dataTransfer.getData("text");
//设置和接收URL
event.dataTransfer.setData("URL", "http://www.wrox.com/");
var url = event.dataTransfer.getData("URL");

IE只定义了"text"和"URL"两种有效的数据类型,而HTML5则对此加以扩展,允许指定各种MIME类型。考虑到向后兼容,HTML5 也支持"text"和"URL",但这两种类型会被映射为"text/plain"和"text/uri-list"。

实际上,dataTransfer 对象可以为每种MIME 类型都保存一个值。换句话说,同时在这个对象中保存一段文本和一个URL 不会有任何问题。不过,保存在dataTransfer 对象中的数据只能在drop事件处理程序中读取。如果在ondrop 处理程序中没有读到数据,那就是dataTransfer 对象已经被销毁,数据也丢失了。

在拖动文本框中的文本时,浏览器会调用setData()方法,将拖动的文本以"text"格式保存在dataTransfer 对象中。类似地,在拖放链接或图像时,会调用setData()方法并保存URL。然后,在这些元素被拖放到放置目标时,就可以通过getData()读到这些数据。当然,作为开发人员,你也可以在dragstart 事件处理程序中调用setData(),手工保存自己要传输的数据,以便将来使用。

将数据保存为文本和保存为URL 是有区别的。如果将数据保存为文本格式,那么数据不会得到任何特殊处理。而如果将数据保存为URL,浏览器会将其当成网页中的链接。换句话说,如果你把它放置到另一个浏览器窗口中,浏览器就会打开该URL。

Firefox 在其第5 个版本之前不能正确地将"url" 和"text" 映射为"text/uri-list" 和"text/plain"。但是却能把"Text"(T 大写)映射为"text/plain"。为了更好地在跨浏览器的情况下从dataTransfer 对象取得数据,最好在取得URL 数据时检测两个值,而在取得文本数据时使用"Text"。

var dataTransfer = event.dataTransfer;
//读取URL
var url = dataTransfer.getData("url") ||dataTransfer.getData("text/uri-list");
//读取文本
var text = dataTransfer.getData("Text");
运行一下 注意,一定要把短数据类型放在前面,因为IE 10 及之前的版本仍然不支持扩展的MIME 类型名,而它们在遇到无法识别的数据类型时,会抛出错误。

16.2.4 dropEffect与effectAllowed

利用dataTransfer 对象,可不光是能够传输数据,还能通过它来确定被拖动的元素以及作为放置目标的元素能够接收什么操作。为此,需要访问dataTransfer 对象的两个属性:dropEffect 和effectAllowed。 其中,通过dropEffect 属性可以知道被拖动的元素能够执行哪种放置行为。这个属性有下列4个可能的值。
  • "none":不能把拖动的元素放在这里。这是除文本框之外所有元素的默认值。
  • "move":应该把拖动的元素移动到放置目标。
  • "copy":应该把拖动的元素复制到放置目标。
  • "link":表示放置目标会打开拖动的元素(但拖动的元素必须是一个链接,有URL)。
在把元素拖动到放置目标上时,以上每一个值都会导致光标显示为不同的符号。然而,要怎样实现光标所指示的动作完全取决于你。换句话说,如果你不介入,没有什么会自动地移动、复制,也不会打开链接。总之,浏览器只能帮你改变光标的样式,而其他的都要靠你自己来实现。要使用dropEffect属性,必须在ondragenter 事件处理程序中针对放置目标来设置它。 dropEffect 属性只有搭配effectAllowed 属性才有用。effectAllowed 属性表示允许拖动元素的哪种dropEffect,effectAllowed 属性可能的值如下。
  • "uninitialized":没有给被拖动的元素设置任何放置行为。
  • "none":被拖动的元素不能有任何行为。
  • "copy":只允许值为"copy"的dropEffect。
  • "link":只允许值为"link"的dropEffect。
  • "move":只允许值为"move"的dropEffect。
  • "copyLink":允许值为"copy"和"link"的dropEffect。
  • "copyMove":允许值为"copy"和"move"的dropEffect。
  • "linkMove":允许值为"link"和"move"的dropEffect。
  • "all":允许任意dropEffect。
必须在ondragstart 事件处理程序中设置effectAllowed 属性。

假设你想允许用户把文本框中的文本拖放到一个<div>元素中。首先,必须将dropEffect 和effectAllowed 设置为"move"。但是,由于<div>元素的放置事件的默认行为是什么也不做,所以文本不可能自动移动。重写这个默认行为,就能从文本框中移走文本。然后你就可以自己编写代码将文本插入到<div>中,这样整个拖放操作就完成了。如果你将dropEffect 和effectAllowed 的值设置为"copy",那就不会自动移走文本框中的文本。

Firefox 5 及之前的版本在处理effectAllowed 属性时有一个问题,即如果你在代码中设置了这个属性的值,那不一定会触发drop 事件。

16.2.5 可拖动

默认情况下,图像、链接和文本是可以拖动的,也就是说,不用额外编写代码,用户就可以拖动它们。文本只有在被选中的情况下才能拖动,而图像和链接在任何时候都可以拖动。 让其他元素可以拖动也是可能的。HTML5 为所有HTML 元素规定了一个draggable 属性,表示元素是否可以拖动。图像和链接的draggable 属性自动被设置成了true,而其他元素这个属性的默认值都是false。要想让其他元素可拖动,或者让图像或链接不能拖动,都可以设置这个属性。 例如:
<!-- 让这个图像不可以拖动 -->
<img src="smile.gif" draggable="false" alt="Smiley face">
<!-- 让这个元素可以拖动 -->
<div draggable="true">...</div>
支持draggable 属性的浏览器有IE 10+、Firefox 4+、Safari 5+和Chrome。Opera 11.5 及之前的版本都不支持HTML5 的拖放功能。另外,为了让Firefox 支持可拖动属性,还必须添加一个ondragstart事件处理程序,并在dataTransfer 对象中保存一些信息。
在IE9 及更早版本中,通过mousedown 事件处理程序调用dragDrop()能够让任何元素可拖动。而在Safari 4 及之前版本中,必须额外给相应元素设置CSS 样式–khtml-user-drag: element。

16.2.6 其他成员

HTML5 规范规定dataTransfer 对象还应该包含下列方法和属性。
  • addElement(element):为拖动操作添加一个元素。添加这个元素只影响数据(即增加作为拖动源而响应回调的对象),不会影响拖动操作时页面元素的外观。在写作本书时,只有Firefox 3.5+实现了这个方法。
  • clearData(format):清除以特定格式保存的数据。实现这个方法的浏览器有IE、Fireforx 3.5+、Chrome 和Safari 4+。
  • setDragImage(element, x, y):指定一幅图像,当拖动发生时,显示在光标下方。这个方法接收的三个参数分别是要显示的HTML 元素和光标在图像中的x、y 坐标。其中,HTML 元素可以是一幅图像,也可以是其他元素。是图像则显示图像,是其他元素则显示渲染后的元素。实现这个方法的浏览器有Firefox 3.5+、safari 4+和Chrome。
  •  types:当前保存的数据类型。这是一个类似数组的集合,以"text"这样的字符串形式保存着数据类型。实现这个属性的浏览器有IE10+、Firefox 3.5+和Chrome。

更多实例

DragAndDropExample01

DragAndDropExample02

DragAndDropExample03

DragAndDropExample04

SetDragImageExample01

CustomDraggableExample01