曾几何时,不能判断拖放行为使得很多人抨击Web,认为这是Web较之桌面客户端程序的明显弱点之一。实际上,哪怕是IE6如此老旧的浏览器,它也是支持拖放行为的,只是缺乏更进一步的程序方法支持而已,很多Web设计爱好者在吹牛的时候应该注意到这一点。
HTML5为拖放行为提供了Drag & Drop API,Drag代表拖动,Drop代表放下,用于帮助开发者方便地处理此类事件。今后,我们在浏览器里处理拖放行为就像处理mouseover、mouseout、click事件一样方便。
先看一个HTML5拖放的案例
以前的拖放解决方案
在过去,我们为了在网页上实现拖放行为往往需要花费较大的力气,基本原理就是通过判断鼠标的点击事件和移动的位置来判断。现在,我们向读者介绍两个过去常用的方法来解释拖放行为的技术方案:一种是基本上依靠JavaScript原生代码编程实现,另一种是依靠Web 2.0时代最锋利的前端之刃jQuery来实现。
1. 基本的JavaScript原生实现
通过原生的JavaScript 代码实现拖放行为非常复杂, 需要使用DOM 事件模型里的mousedown、mousemove和mouseup事件,还需要不断获得鼠标坐标,修改元素的坐标位置,性能会成为很大问题。另外,实现时还需要考虑浏览器兼容性问题,并且不支持浏览器页面以外的拖放行为。
2. JavaScript库
jQuery等JavaScript库提供了封装好的拖放处理,解决了JavaScript原生实现的复杂性和兼容性问题。但是这些JavaScript库为了保证兼容性进行了复杂的计算和处理,使用了大量的代码,造成库的体积过大,影响页面的加载速度,增大了网络流量。
Drag & Drop API 的优点
使用Drag & Drop API,可以给我们带来如下好处。
它通过事件的方式让浏览器原生支持拖放行为,我们不再需要编写复杂的JavaScript代码,不再需要考虑千奇百怪的浏览器兼容性问题。
用原生方式取代JavaScript代码的方式也大大减少了页面的大小,让用户能更快地打开页面,减少带宽请求。
它提供了dataTransfer接口来存储数据,允许我们定义拖放时的效果,此外还支持自定义拖放操作。
它支持浏览器页面以外的拖放行为,例如从桌面拖放一个文件到浏览器页面里,这在以前是不可能实现的。
Drag & Drop API 的主要操作
Drag & Drop API主要包含三方面的内容:事件、属性和接口,下面我们对它们一一进行讲解。
1. 主要事件
下面是拖放的相关事件,按照拖放的过程分为7个。
dragstart。拖动开始的事件,需绑定在拖曳对象上。
drag。从dragstart开始后到dragend结束前,在拖动时这个事件不断出现,需绑定在拖曳对象上。
dragend。拖动结束的事件,需绑定在拖曳对象上。
dragenter。拖到当前元素区域内的事件,需绑定在推曳对象所拖曳的对象上。
dragover。拖到当前元素的区域上的事件,这个事件在拖动过程中不断被触发,需绑定在拖曳对象正拖曳到的对象上。
dragleave。拖出当前元素区域的事件,需绑定在拖曳对象刚刚拖曳到的对象上。
drop。在当前元素区域停止拖曳的事件,需绑定在拖曳对象所放置到的对象上。
一个完整的拖放事件过程如下图所示,我们可以轻松地使用上面的事件来处理复杂的拖放行为。
一个完整的HTML5拖放事件过程
2. 相关属性
默认情况下,img和a标签可以进行拖放。如果其他类型的节点也需要支持拖动,必须添加属性draggable="true"。
使一个元素支持拖动其实非常简单,第一要为此元素设置draggable属性,第二需要为它的dragstart事件添加函数来处理拖曳数据。
Drag & Drop API还有一个dropzone属性,用于设置放置目标区域所允许的文件类型和反馈方式,例如用move s:text/plain表示展现数据移动的效果和接受任何文本的放置,用copy f:image/png表示展现数据复制的效果和接受png格式图片的放置。
因此,要使一个元素允许其他拖曳元素放置到它之上,此元素必须要有dropzone属性并且监听drop事件(和上面元素支持拖曳的两个条件非常相似)。不过,我们也可以采用为放置元素自定义事件处理的方式来代替dropzone属性,这就需要我们在dragenter事件里判断此元素是否支持放置以及在dragover事件里指定为用户显示什么样的反馈效果。
3.主要接口
HTML5 使用dataTransfer 接口来支持拖放数据存储, 它的使用方式一般为event.dataTransfer。dataTransfer对象不仅可以用来传递数据,还可以用来指定拖曳对象和放置对象所表现的反馈效果,它包含以下属性和方法。
dataTransfer.effectAllowed[=value]。
该属性返回拖曳对象允许的拖曳时的反馈效果。作为方法时,用于初始化dragenter和dragover事件时允许的dropEffect属性,
可以设置的值如下表所示,其默认值为uninitialized。
dropEffect属性的值及描述:
dropEffect属性的值及描述
这个属性可以在拖曳对象的dragstart事件处理函数中设置,例如下面的代码示例:
dragElement.ondragstart = function(e){
var dt = e.dataTransfer;
dt.effectAllowed = 'link';
};
dataTransfer.dropEffect[=value]。
该属性返回已设置的拖放时反馈效果。
作为方法时,可以设置拖放对象时表现什么样的反馈效果,包含如下值:none、copy、link和 move,这几个值分别代表拖曳对象不能放置在此处、拖曳对象移动到放置对象、拖曳对象被复制到放置对象、拖曳对象被链接到放置对象4种效果。
这4种效果表现为不同的鼠标形状,如下图3-5所示。
不同的鼠标形状
dropEffect的设置可以在放置对象的dragenter或者dragover事件处理函数中处理,如下面的代码所示。
dropElement.ondragover = function(e){
var dt = e.dataTransfer;
dt.dropEffect = 'link';
e.preventDefault();
};
另外,如果该效果与起初设置的effect-Allowed效果不符,则拖放操作将会失败。
dataTransfer.items。返回一个关于拖曳数据的DataTransferItemList对象。
dataTransfer.setDragImage(element, x, y)。指定拖曳元素时随鼠标移动的图片,x、y分别是图片相对于鼠标的横坐标和纵坐标。
dataTransfer.addElement(element)。将元素添加到被拖曳的列表里。如果你想让某个元素跟随被拖曳元素一同被拖曳,可以使用这个方法。
dataTransfer.types。返回在dragstart事件触发时为元素存储数据的格式。如果是系统文件的拖曳,则返回files。
dataTransfer.setData(format, data)。为元素添加数据,在dragstart事件触发时可以用它为被拖曳元素存储数据。setData的数据格式一般有两种:text/plain(设置format为字符串text即可,主要用于文本数据)和text/uri-list(设置format为字符串url即可,主要用于url)。
data=dataTransfer.getData(format)。返回存储的数据。如果数据不存在,则返回空字符串。
dataTransfer.clearData([format])。删除指定格式的数据。如果不指定格式,则删除所有数据。
dataTransfer.files。如果是拖曳系统文件,返回正在被拖曳的文件列表对象。可以通过它获得所拖曳的文件数据。
dataTransfer对象提供的属性和方法使得我们自定义处理拖放操作成为可能。
文件拖放上传实例
下面的代码清单是一个JavaScript代码示例,演示了如何将一个元素拖放到另一个元素上,并且在鼠标放下时改变放置目标元素的状态。
var dragElement = document.getElementById('drag-element');
var dropElement = document.getElementById('drop-element');
dragElement.setAttribute('draggable', 'true'); //允许dragElement元素可以拖动
dragElement.ondragstart = function(e){
var dt = e.dataTransfer;
dt.effectAllowed = 'link'; //设置反馈效果为链接形式
};
dropElement.ondragover = function(e){
e.preventDefault();
}
dropElement.ondrop = function(){
dropElement.innerHTML = '拖曳结束'; //改变放置目标元素的状态
}
dropElement.ondragenter = function(e){
var dt = e.dataTransfer;
dt.dropEffect = 'link';
e.preventDefault();
};
可能犯的一个错误
创建拖放事件监听程序时,一定要阻止默认行为,尤其是在dragover事件中一定要执行preventDefault(),否则drop事件不会被触发,同时dropEffect也不会生效。
下面是正确的代码:
dropElement.addEventListener('dragover', function(e){
e.preventDefault();
}, false);
请注意,dragstart事件里不能阻止默认行为,否则拖曳行为就不会发生。