永利网址HTML5 — 让拖放变的流行起来

HTML5 — 让拖放变的流行起来

2015/12/29 · HTML5 · 4
评论
·
拖放

原文出处: 韩子迟   

先上 Demo,尽量用
chrome,代码可参考
Github

在 HTML5 出现之前,页面元素的拖放需要监听 mousedown、mouseover 以及
mouseup 等一系列事件,然后改变元素的相对位置来实现这一效果。HTML
DnD(Drag-and-Drop)API 的出现,使得拖放变的简单。但是由于 DnD
尚处在草案阶段,各浏览器对其规范并未统一,有些事件在不同浏览器中会出现不同效果。

要使用
DnD,需要明确两件事情,一是需要拖动的元素,二是可放置拖动元素的位置。拖放无非是将元素从一个位置拖到另一个位置。

 HTML5 出现之前,页面元素的拖放需要监听
mousedown、mouseover 以及 mouseup
等一系列事件,然后改变元素的相对位置来实现这一效果。HTML
DnD(Drag-and-Drop)API 的出现,使得拖放变的简单。但是由于 DnD
尚处在草案阶段,各浏览器对其规范并未统一,有些事件在不同浏览器中会出现不同效果。

Drag


首先我们需要指定要拖动的元素,设置方式很简单,给该 DOM 元素设置
draggable 属性,属性值设置为 true。比如这样:

<code> <img src=”images/0.jpg” draggable=”true” id=”img0″/>
</code>

1
2
3
<code>
  <img src="images/0.jpg" draggable="true" id="img0"/>
</code>

事实上,以上代码多此一举了,页面中的图片(img)、链接(带 href 的 a
标签)以及文本默认即为可拖动。为了统一,最好还是都加上该 draggable
属性为好。

draggable 属性还有两个值,分别是 falseauto,顾名思义,false
即设置为不可拖动,auto 即为浏览器默认值。

当我们左键点击(按下)可拖动的 DOM 元素,轻轻移动,即触发 ondragstart
事件,该事件只会触发一次。通常我们会在 ondragstart
事件中记录正在被拖动的元素信息(ondrop 的时候好对其进行处理)。比如 demo
中记录了正在被拖动的元素 id:

for (var i = lis.length; i–; ) { lis[i].ondragstart = function(e) {
e.dataTransfer.setData(‘id’, e.target.id); }; }

1
2
3
4
5
for (var i = lis.length; i–; ) {
  lis[i].ondragstart = function(e) {
    e.dataTransfer.setData(‘id’, e.target.id);
  };
}

ondragstart 事件触发后,直到拖放事件结束,会一直触发 ondrag 事件。

要使用
DnD,需要明确两件事情,一是需要拖动的元素,二是可放置拖动元素的位置。拖放无非是将元素从一个位置拖到另一个位置。

Drop


其次我们需要明确被拖动元素可放置的位置,ondragover
事件规定在何处放置被拖动的数据。
默认地,无法将元素放置到其他元素中,如果需要设置允许放置,我们必须阻止对元素的默认处理方式:

var dus = document.querySelector(‘.dustbin’); dus.ondragover =
function(e) { e.preventDefault(); };

1
2
3
4
5
var dus = document.querySelector(‘.dustbin’);
 
dus.ondragover = function(e) {
  e.preventDefault();
};

当元素被拖动到某一元素上时,即会触发后者的 ondrop
事件,如果需要正确触发 ondrop 事件,还需要取消一些 DnD
事件的默认行为:

dus.ondrop = function(e) { // 调用 preventDefault()
来避免浏览器对数据的默认处理(drop 事件的默认行为是以链接形式打开)
e.preventDefault(); e.stopPropagation(); // 兼容ff var id =
e.dataTransfer.getData(‘id’) , node = document.getElementById(id);
node.parentNode.removeChild(node); };

1
2
3
4
5
6
7
8
9
10
dus.ondrop = function(e) {
  // 调用 preventDefault() 来避免浏览器对数据的默认处理(drop 事件的默认行为是以链接形式打开)
  e.preventDefault();
  e.stopPropagation(); // 兼容ff
 
  var id = e.dataTransfer.getData(‘id’)
    , node = document.getElementById(id);
 
  node.parentNode.removeChild(node);
};

有些文献中说要取消 ondragenter()
事件的默认行为,楼主在实际操作中并未发现这点。

Drag

事件


上面已经提到了 DnD 中的三个事件,dragstartdragover 以及
drop,其实 DnD 还有几个事件,它们的发生顺序是:

dragstart(drag元素) -> drag(drag元素) -> dragenter(drop元素) ->
dragover(drop元素) -> dragleave(drop元素) -> drop(drop元素) ->
dragend(drag元素)

1
dragstart(drag元素) -> drag(drag元素) -> dragenter(drop元素) -> dragover(drop元素) -> dragleave(drop元素) -> drop(drop元素) -> dragend(drag元素)

不难理解,拖放事件开始时触发 ondragstart
事件,当被拖动元素进入可放置的元素时,触发 ondragenter 事件(ondragenter
并不是在两个元素相交时即触发,而是该被拖拽元素在目标元素上移动一段时间后才触发),之后一段事件会持续触发
ondragover 事件(可参考
mouseover),当被拖动元素离开可放置元素的一瞬间,触发 ondragleave(和
ondragenter 对应)
事件,当松开鼠标并且被拖拽元素正好在可放置元素上时,触发 ondrop
事件,当拖放事件结束时,触发 ondragend(和 ondragstart 对应)
事件,无论拖放操作是否成功,均会触发该事件。

首先我们需要指定要拖动的元素,设置方式很简单,给该 DOM 元素设置
draggable 属性,属性值设置为 true。比如这样:

dataTransfer


拖动过程中,回调函数接受的事件参数,有一个 dataTransfer
属性。它指向一个对象,包含了与拖动相关的各种信息。

dataTransfer 对象主要有两种方法:getData() 和
setData(),需要注意的是,只有在 dragstart 以及 drop
事件中使用这两个方法。不难想象,getData() 可以取得由 setData()
保存的值。setData() 方法的第一个参数,也是 getData()
方法唯一的一个参数,是个字符串,表示保存的数据类型,取值为 ‘text’ 或
‘URL’。IE 只定义了 ‘text’ 和 ‘URL’ 两种有效的数据类型,而 HTML5
则对此加以扩展,允许指定各种 MIME 类型。

在拖动文本框中的文本时,浏览器会自动调用 setData() 方法,将拖动的文本以
‘text’ 格式保存在 dataTransfer
对象中,类似地,在拖放链接或者图像时,会自动调用 setData() 将 URL
信息保存,如果有需要,在 drop 事件中可以用 getData()
读取浏览器保存的值。

但是这似乎并没有什么卵用,我们在实际开发中多数还是对 DOM
的操作,于是多数情况下我们在 dragstart 事件处理程序中调用
setData(),手工保存自己要传输的数据,然后在 drop 事件中读取,有点像
jQuery 的 data 事件。

<img src=”images/0.jpg” draggable=”true” id=”img0″/>

dropEffect 与 effectAllowed


dropEffecteffectAllowed 是前面说的 dataTransfer
对象的两个属性,有啥用?简单地说,有两个用处,一是可以设置元素被拖拽时的鼠标样式,二是可以设置元素是否可被放置。

这里我测试了三款浏览器,chrome、ff 以及 uc,chrome 和 uc 表现相似。

一般我们将元素脱离原来的位置,鼠标手势会变成
“禁手”,直到元素被拖到可放置区域上。

永利网址 1

但是 ff 不然,在 ff 中,元素在拖动的过程中不会显示 “禁手”。

当元素被拖到可放置区域上时,默认鼠标手势如下。

永利网址 2

其实通过设置 dropEffecteffectAllowed
总共能设置三种鼠标手势(move, copy,以及 link),分别如下(move
和默认貌似一样):

永利网址 3

需要在 ondragstart 方法中设置 effectAllowed,在 ondragover
方法中设置 dropEffect。具体可以参考 demo代码。

我们也可以对 dropEffect 和 effectAllowed 的值进行设定,让某 drop
元素只能放 move 元素,或者 copy
元素等。具体可以看下这篇,HTML5魔法堂:全面理解Drag & Drop
API
,讲的很好。取值也可以参考高程
484 页。

总之要知道的是,DnD 并不会帮你完成 copy 或者 move
的任何操作,而是需要用户在 DnD 过程中,记录需要操作的对象信息,然后在
drop 事件中完成 copy 或者 move 等的操作。

事实上,以上代码多此一举了,页面中的图片(img)、链接(带 href 的 a
标签)以及文本默认即为可拖动。为了统一,最好还是都加上该 draggable
属性为好。draggable 属性还有两个值,分别是 false 和
auto,顾名思义,false 即设置为不可拖动,auto 即为浏览器默认值。

Tricks


还有几个实践过程中发现的问题。

Demo 在 ff
中打开,图片拖到空处,会自动在新标签中打开图片,尽管我已经在各种事件中加上了
preventDefault(),尚不清楚原因。

如果可拖拽元素,初始在一个可放置元素内部,先把元素拖出去,再放回来,将会触发
ondrop 事件,但是 e.target 却是被拖拽的元素。如果放置在其他元素,target
会指向被放置的元素,而不是拖拽元素。这点可以通过判断 target
元素得到解决。关于这点可以看下 w3cschool 的这个
demo,打开控制台,将图片拖出去,再拖回来,控制台会打印出错误,显然代码没有考虑到这一点。


Read More:

永利网址 4

当我们左键点击(按下)可拖动的 DOM 元素,轻轻移动,即触发 ondragstart
事件,该事件只会触发一次。通常我们会在 ondragstart
事件中记录正在被拖动的元素信息(ondrop 的时候好对其进行处理)。比如 demo
中记录了正在被拖动的元素 id:

for (var i = lis.length; i–; ) {
lis[i].ondragstart = function(e) {
e.dataTransfer.setData(‘id’, e.target.id);
};
}

      ondragstart 事件触发后,直到拖放事件结束,会一直触发 ondrag 事件。

Drop

其次我们需要明确被拖动元素可放置的位置,ondragover
事件规定在何处放置被拖动的数据。
默认地,无法将元素放置到其他元素中,如果需要设置允许放置,我们必须阻止对元素的默认处理方式:

var dus = document.querySelector(‘.dustbin’);

dus.ondragover = function(e) {
e.preventDefault();
};

当元素被拖动到某一元素上时,即会触发后者的 ondrop 事件,如果需要正确触发
ondrop 事件,还需要取消一些 DnD 事件的默认行为:

dus.ondrop = function(e) {
// 调用 preventDefault() 来避免浏览器对数据的默认处理(drop
事件的默认行为是以链接形式打开)
e.preventDefault();
e.stopPropagation(); // 兼容ff

var id = e.dataTransfer.getData(‘id’)
, node = document.getElementById(id);

node.parentNode.removeChild(node);
};

有些文献中说要取消 ondragenter()
事件的默认行为,楼主在实际操作中并未发现这点。

事件

上面已经提到了 DnD 中的三个事件,dragstart、dragover 以及 drop,其实 DnD
还有几个事件,它们的发生顺序是:

dragstart(drag元素) -> drag(drag元素) -> dragenter(drop元素) ->
dragover(drop元素) -> dragleave(drop元素) -> drop(drop元素) ->
dragend(drag元素)

不难理解,拖放事件开始时触发 ondragstart
事件,当被拖动元素进入可放置的元素时,触发 ondragenter 事件(ondragenter
并不是在两个元素相交时即触发,而是该被拖拽元素在目标元素上移动一段时间后才触发),之后一段事件会持续触发
ondragover 事件(可参考
mouseover),当被拖动元素离开可放置元素的一瞬间,触发 ondragleave(和
ondragenter 对应) 事件,当松开鼠标并且
被拖拽元素正好在可放置元素上时,触发 ondrop 事件,当拖放事件结束时,触发
ondragend(和 ondragstart 对应)
事件,无论拖放操作是否成功,均会触发该事件。