使用HTML5本地 Drag和Drop API(native API)

人人都爱使用方便、.具有互动的用户界面。并且随着智能手机的引入,用户的期望瞬间高了一大截。他们希望网站一目了然,交互动作尽人皆知。总之,他们希望你的网站使用起来超级简单。

让你的用户能在你的网站实现拖拽之类的操作吧,这会让你的网站更加具有交互性。因为人们都知道如何把东西从X拖到Y位置,知道如果把A拖到B前面的话,那么A就会先显示出来。

处理拖动,拖入之类的操作以前总是javascript的事情,开发者们早先也有自己的方法构建交互动作或者使用预制解决方案。随着HTML5 Drag和Drop API的出现,开发者们可以使用本地事件和属性来处理这些交互动作了。

简要介绍

那么就让我们浏览一下这些API好让我们对它的工作方式有个大致的了解吧。

native API让我们可以通过使用draggable="true"属性来让想要的元素可以拖动。一些元素甚至不需要做任何修改,已经是默认可拖拽的了(比如文本和图片)。

默认的, 如果可拖动元素被拖动,只有form元素,比如input可以接手这些元素作为拖入。你或许早先见过这个:如果你选中一些文字并且将它们拖进一个textarea,文本就会被复制到textarea。

native API也被用来处理从位于OS上的外部区域拖动到drop区域的操作。几乎所有优秀的内容管理系统都提供了拖出和拖入上传内容功能。因为这些元素都是外部的,所以你只需要搞清楚拖入区域(当然还需要一个可兼容的浏览器)。

一笔带过移动端

目前native API还不支持移动端。这在今后可能会有所改变,最好是在PC上看演示实例,这样你才可以明白它都是怎么工作的。

Drop和Drag API 事件(Events)

native API提供下列可帧听event.。这些event会在可拖拽元素或可拖放区域被应用,在设置的同时触发。

当这些event被成功触发,我们就开始使用一个本地对象(我们称之为event)。这个对象拥有的关于event的信息比event自身还要多,同时这个对象也让你能够使用datatransfer对象。我们大部分方法和属性都需要通过后者来设置。

我们需要给每个event都设置一个回调,这样才能和这些API进行交互:

  1. // add a handler to trigger on dragstart
  2. document.addEventListener('dragstart', function(event) {
  3. // add your dragstart code here
  4. }, false);
 

 

 拖拽相关event

这些event只有在可拖拽元素里才会被触发

dragstart

一旦我们开始拖拽某一个元素,这个event就被触发。在这里我们需要告诉API我们将拖拽的元素和一些别的参数。使用setData()方法来设置需要保存的数据,为可拖拽元素设置effectAllowed()属性,使用setDragImage()来定义可拖拽助手。

drag

这个event在拖拽过程中被持续触发。发生的次数取决于浏览器。这个被用来监测被拖拽元素的位置。

dragend 

这个event在可拖拽元素被放置好后触发(不考虑放置位置),而且一般会在放置区域的drop event之后触发。在拖动的时候,你可以使用这个event来重设样式或者别的页面展示动作。dragend event可以使用被拖拽元素,所以可以在拖拽完毕后进行一些计算(比如通过查看新添加元素来看看drop evnet是否触发,然后移除初始的元素。)

拖放相关event

这些event只有在被定义为拖放目标的元素中才会被触发。(或者默认是拖放目标的元素,比如form元素)

dragenter

只在可拖拽元素进入拖放区域时被触发一次。这个event会在50%可拖拽元素进入拖放区域的时候被触发。

这个event设置拖放区域的dropEffect()。拖放至非form元素默认不采取任何操作。你需要手动设置event.preventDefault()和event.stopPropagation()来告诉API本次拖放操作可以执行。

你可以将拖拽元素设置的dataTransfer对象的effectAllowed值与拖放区域的dropEffect的值进行比较。如果这些值不能一直协同工作(比如一个是copy,另一个是link),那么浏览器就不会放置该元素(即使你手动设置过了preventDefault和stopPropagation)。

你可以使用types属性来获取dragstart中设置的所有数据类型。你无法看到具体数据但是你可以看到数据是什么类型。同时可以使用contains方法来查看某一种特定类型数据是否被设置。这个是通过event.dataTransfer.typs.contatins(type)方法完成的。比如你可以用这种方式来看看text/html中的某样东西是否设置过了。

可以用过设置一些类或触发动作来让用户知道可拖动元素已经被成功放入拖放区域(通常是改变拖放区域的样式来证明操作已完成)。

dargover

这个event和dragenter基本一样,但是会在可拖放元素进入拖放区域后持续触发。如果你需要知道被拖动元素的准确位置,那么这个event再适合不过了(因为它的数值不断更新)。

dragleave

这个evnet在可拖拽元素离开拖放区域的时候被触发。它通常被用来移除dragenter或者dragover的样式。只会在可拖动元素与拖放区域不再有重合部分时被触发一次。

drop

这个event在可拖动元素被松开同时拖放区域可接受此元素时被触发。这个只有在可拖动元素和拖放区域有正确的dropeffect和effectAllowed值时才会被触发。在放置时需要通过getdata方法获取有关信息。

Drag 和 Drop API 方法

在处理本地drag和drop API时,我们主要是和dataTransfer对象打交道。它作为回调函数的一部分呈现在我们面前同时也提供了几个别的可用函数。

setData

这个方法通过调用event.dataTransfer.setData(type.data)方法来设定将会从可拖动元素搜集的数据。你需要给其传递将被搜集的数据类型(type)以及数据本身。必须在dargstart中设置否则会不起作用。它的值只会在drop event之后被采集。

type最好是applicable data type。但是假如你使用的是Chorme、Safari、Firefox,那么你可以使用像text/html、text/uri-list这些类型。如果你用的是IE,那么你需要将其设置为Text或者URL(必须完全一致否则会出错)。

同时建议data是你想保存的数据。你可以保存一个URL,一大块HTML或者任何别的数据。每一个type中只能设置一份data。比如你在text/html中保存的是一些HTML,那么你不能再通过setData()方法来保存一个新的data。因为这样会使旧data被覆盖。

getData

它和setData()相对应,是用来在startdrag event期间收集被拖动元素数据的。通过调用event.dataTransfer.getData(type)来采集需要的指定类型信息。

你会经常用到event.dataTransfer.types来查看所设置的类型以弄清楚传递的数据形式。如果你试图使用一种未被设置过的类型,IE将会报错。

这个方法只有在drop event中API提供了值之后才可以使用,这样才可以采集数据。(这是为了在传输过程中保护数据)

clearData

正如名字,它使用event.dataTransfer.clearData(type)来清除使用setData设置的数据。你需要制定将被删除的数据类型(比如:text/html或者url)。这个方法只能在dragstart event中使用。

setDragImage

这个方法使用event.dataTransfer.setDataImage()来设置被拖动图片拖动过程中显示的样子。默认显示为半透明的状态。你可以使用这个方法设置成自己需要的样子。这个方法在除IE之外的浏览器中都可以运行,而且目前也没有让IE支持的打算。

Drag 和 Drop API属性

 下面是一些在dataTransfer对象中可以设置的属性。我们使用从event回调中传递的event变量来设置这些属性。

effectAllowed

这是可拖动元素特有的属性。它将告诉API drag event相关信息以及光标的展示形式(取决于OS和浏览器)。它通过给dragStart event中的evnet.dataTransfer.effectAllow赋值进行调用同时从copy,move,link,copyLink,copyMove,linkMove,all,none或者uninitialized获取possible value。

如果该值和dropEffect不匹配,那么它将阻止调用drop event(这样确保了拖放的准确性)。

dropEffect

这个属性制定了拖放区域以及可被拖放区域接受的拖放元素。它应该在dragenter或dragover期间通过event.dataTranser.dropEffecr被赋值。dropEffect从copy.link,move或none中获取possible value.

和effectAllow一样,如果值和effectAllow不匹配,那么它将阻止调用effectAllow event(这样确保了拖放的准确性).

files

这个属性包含了一个所有被设置过的本地文件列表。通过event.dataTransfer.files调用。只有被请求的文件才能从系统拖动到页面中(比如将桌面上的图片拖动到网页的上传栏中)。这个属性在网页上的元素被拖动时将是空的。(比如你拖动一张图片,那么不会为files设置任何数据)。

这步也可以检查我们是否拥有文件,如果有,那么可以通过使用fileReader 对象来读取以及处理文件内容。

effectAllow 和 dropEffect实战

如果你想了解这些属性的具体用法,那么应该看看下面的CodePen demo(我尽量做个demo放文章中,但是不一定有时间,谁做了私信我)。

点我看demo.

我们定义了多个不同的可拖拽项目同时设置它们可以被放置的区域,我们也创建了几个拖放区域并设置了这些区域可以接收的项目类型。正确的设置这些属性可以让浏览器明白哪些可拖拽元素能被放置进来。

虽然IE支持effectAllowed和dropEffect,但是它自身无法实现只允许合适的元素被拖放到拖放区这个功能。Chrome,Safari,Firefox会限制被拖动的项目同时阻止不合适的拖放,拒绝触发drop event。在IE中你可能需要通过在drop event触发时比较一些值来人工判断是够拒绝拖放。

用native API构建一些东西

这些API处理的信息太多了,因此我们将所有东西在下面的练习中结合起来。

native API主要考虑的是dragable和dropable元素之间的交互以及其中的数据传递。native API不关心你正在移动两个元素从而交换它们的位置。它跟在乎的是它们的数据。而这让native API与众不同。

最有用的是native API可以处理多种数据类型和从多个位置获取的数据。

数据类型包括:

  • 普通字符串
  • Text/HTML 内容
  • URL列表
  • 单个或多个文件
  • 多种其它类型/自定义类型

数据来源包括:

  • 来自被拖动或放置的元素
  • 来自另一个标签页,窗口,浏览器的可拖放元素
  • 来自本地源(比如你的电脑)

处理Drag和Drop之间的数据

native API提供了对可拖放元素的基础支持。API让你可以通过event知道drag是什么时候发生的,不想JQurey UI,n你需要手工移动/复制元素来校准API。

这是因为当你移动一个元素的时候,你的触发器是设置dragStart event,你已经给它设置好了想要传输的数据(还包括希望可拖动元素拥有的一些效果,比如复制,移动等等)。当最终放置好可拖动元素时,比如放到了正确的位置,它就会触发拖放区域的drop event.它承载的是你想移动的数据而不是UI元素(UI元素需要用JavaScript手动调整)。

让我们看一个例子,这样你就能清楚的知道它是怎么工作的。

例:一个可拖放拼图游戏

通过这个例子了解我们是如何利用API在同一个页面的两个不同元素之间传递数据的。

点我看例子。(谁做了这个demo告诉我)

本例中我们定义了一些区域,左侧存放的是我们的一些碎片,而右侧是一些空的可放置区域。这个游戏就是将左边的碎片拖到右边,完成这个拼图。

设置dragStart数据

在本游戏的dragStart event中,我们先是设置了effectAllow来告诉元素它接受一个copy类的拖动。

然后我们搜集scr(图片源)和outerHTML(HTML节点)并将它们放入我们的数据传输对象中,比如text/url-list和text/html。如果是IE,我们就只保存被拖动元素的src并保存为它的text格式。

  1. var dragItem;
  2. // triggered draggable as we start dragging
  3. function dragStart(event) {
  4. drag = event.target;
  5. dragItem = event.target;
  6.  
  7. // set the effectAllowed for the drag item
  8. event.dataTransfer.effectAllowed = 'copy';
  9.  
  10. var imageSrc = $(dragItem).prop('src');
  11. var imageHTML = $(dragItem).prop('outerHTML');
  12.  
  13. // check for IE (it supports only 'text' or 'URL')
  14. try {
  15. event.dataTransfer.setData('text/uri-list', imageSrc);
  16. event.dataTransfer.setData('text/html', imageHTML);
  17. } catch (e) {
  18. event.dataTransfer.setData('text', imageSrc);
  19. }
  20.  
  21. $(drag).addClass('drag-active');
  22. }
 

 正确的effcetAllow/dropEffect

在我们放置拼图碎片之前,dargEnter和dragOver就已经被触发了。记住,为了能放置元素,我们需要通过返回false/prevent default来告诉浏览器这个元素可以被放置。这两个函数都会设置拖放区的dropEffect为copy,这意味着它会接受所有effectAllowed为copy的可拖动元素。(我们的元素恰好就是这样的)。我现在提及这点是因为如果他们不匹配,drop event就不会触发,而且拖动也不会被取消。

收集drop数据

当我们在右侧拖放时,我们使用getData方法来提取text/url-list和text/htm的数据配置。如果没有(在用IE时)我们就只提取text数据。

根据拥有的数据不同我们也需要做一些差异化的设置。如果我们使用dataHTML则表明我们在使用完全支持的浏览器,我们可以使用所有的可拖拽节点。如果这样,我们就将所有元素添加到放置区域,这样就完成了放置。

加入没有使用完全支持的浏览器,我们需要克隆在dragStart evnet中的dragItem来获取节点。然后将添加到放置区已完成放置。

  1. // called when draggable is dropped on droppable
  2. function drop(event) {
  3.  
  4. drop = this;
  5. $(drop).removeClass('drop-active');
  6.  
  7. var dataList, dataHTML, dataText;
  8.  
  9. // collect our data (based on what browser support we have)
  10. try {
  11. dataList = event.dataTransfer.getData('text/uri-list');
  12. dataHTML = event.dataTransfer.getData('text/html');
  13. } catch (e) {
  14. dataText = event.dataTransfer.getData('text');
  15. }
  16.  
  17. // we have access to the HTML
  18. if (dataHTML) {
  19. $(drop).empty();
  20. $(drop).prepend(dataHTML);
  21.  
  22. // check if this element is in the right spot
  23. checkCorrectDrop(drop, dragItem);
  24.  
  25. // see if the final image is complete
  26. checkCorrectFinalImage();
  27. }
  28.  
  29. // only have access to text (old browsers + IE)
  30.  
  31. else {
  32. $(drop).empty();
  33. $(drop).prepend($(dragItem).clone());
  34.  
  35. // check if this element is in the right spot
  36. checkCorrectDrop(drop, dragItem);
  37.  
  38. // see if the final image is complete
  39. checkCorrectFinalImage();
  40. }
  41.  
  42. event.preventDefault();
  43. event.stopPropagation();
  44. }
 

 完成这个游戏

两个drop evnet都调用了checkCorrectDrop(drop,dragItem)和checkCorrectFinalImage()函数。他们被用来为我们的游戏服务。

checkCorrectDrop()函数用来检查自定义属性data-value是否和drag元素以及drop区域一致。如果一致,那么说明这块碎片是放在这里的,那么将会用绿色border高亮。

checkCorrectImage()函数用来检查所有的拼图碎片是否都在正确的位置上。如果我们的正确元素和可拖动元素一样多,那么说明这个拼图游戏完成了。

从其它标签页和本机移动数据

通过native API,你可以定义接收拖动元素的拖放区域。看起来一些jQuery UI也能实现,不过jQuery U无法实现从外部标签页,窗口或浏览器直接拖动元素到我们的放置区域。

你或许已经在很多低昂看过这个功能了。有不少网站允许你从一个标签页拖动一张图片到放置区,同时接收网站会考虑其中相关的交互动作。

为了使用dragging和dropping,需要对拖放区进行配置好让其明白如何承载将接受的数据。(基本上任何可拖动元素,比如图片,文本,链接和内容都会被拖动到拖放区。)

从本地拖动内容到一个网页并自动上传是其中一个革命性的功能,没了这个功能你都不知道该怎么办了。

大部分CMS(比如WordPress)支持通过drag-and-drop交互界面来上传内容。其它一些如Gmail之类的App也提供了此功能,让你能够直接将内容拖到一个区域然后自动粘贴或保存以备用。

例:从外部源拖动图片

下面的例子会承载来自其它标签页/窗口的拖放,让放置区采集图片用以展示。

如果你想知道它是怎么工作的,看看下面的demo.

点我看demo.

这个例子重点在于处理被拖动元素。不像别的例子,我们必须对我们做出的拖动配置数据,这里我们只需要采集数据同时决定如何处理它就够了。

在我们的drop函数中,我们从使用getData(format)方法采集dataTransfer对象的数据开始。

  1. // get the URL of elements being dragged here
  2. try {
  3. dataValue = event.dataTransfer.getData('text/uri-list');
  4. dataType = 'text/uri-list';
  5. } catch (e) {
  6. dataValue = event.dataTransfer.getData('URL');
  7. dataType = 'URL';
  8. }
 

 我们将这段放在异常处理中,主要是因为IE浏览器不能理解getData(),如果我们试图使用的话,浏览器就会报错并结束运行。

如果我们可以获得text/uri-list形式的数据,我们就采集该数据;如果不可以,我们就返回采用URL属性。

大部分被拖动元素,比如图片,链接或数据会有多种数据类型。因为我们只关系这些元素的URL,所以可以正常工作。

如果我们拥有了dataValue,这意味着用户在放置区放了一些东西。我们现在需要弄清楚到底是什么东西。我们只想接收图片,但是API无法识别图片链接和普通链接,所以我们需要做一些检查确保放进来的是图片。

  1. // determine if our URL is an image
  2. imageDropped = false;
  3. var imageExtensions = ['.jpg','.jpeg','.png','.bmp','.gif'];
  4. for (i = 0; i< imageExtensions.length; i++) {
  5. if (dataValue.indexOf(imageExtensions[i]) !== -1) {
  6. // create our image to add
  7. var image = '<img src="' + dataValue + '">';
  8. drop.append(image);
  9. imageDropped = true;
  10. break;
  11. }
 

 我们创建了一个图片文件扩展名列表,比如.jpg和.png,然后检查链接中是否有出现这些扩展名。如果有,我们可以确信这是一张图片,我们就使用采集的值作为源绘制一张新的图片。

处理本地拖放元素

本地拖放元素有一点不一样,我们不使用getData(format)方法,我们使用files()方法。这个方法会提供一个被放置的元素列表,这样我们可以遍历这些元素。

  1. var dataFiles = event.dataTransfer.files;
  2. var dataOutput = [];
  3. if (dataFiles) {
  4. for (i =0; i < dataFiles.length; i++) {
  5. // do processing here
  6. }
  7. }
 

 比如我们想遍历所有被放置元素以检查其中是否有图片。当我们遍历每一个文件的时候,我们会处理一堆属性,其中就有type属性,该属性指出了这些元素扩展类型。

  1. // check if this is an image
  2. if (dataType.match('image.*')) {
  3. // it's an image, process further
  4. }
 

 如果匹配到了图片,我们就创建一个新的fileReader对象用来将文件读入内存。然后使用readAsDataURL(item)方法写入我们的文件,当完成后会出发onload event。我们会在后面用到。

  1. // read into memory
  2. var reader = new FileReader();
  3.  
  4. // load element
  5. reader.readAsDataURL(dataItem);
 

 现在我们要做的就是采集file reader的结果并将其添加进DOM。我们就成功的将一个图片从本机拖动到网页中了。

  1. // when our image is loaded
  2. reader.onload = (function(theFile) {
  3. return function(e) {
  4. var url = e.target.result;
  5.  
  6. drop.append('<img src="' + url + '" title="' + dataName + '"/>');
  7. messageContainer.append('
  8. <p><strong>Successfully dropped an image from your desktop</strong></p>
  9. ');
  10. };
  11. })(dataItem);
 

 纵览浏览器支持情况

正如名字所传达的信息,这些API给开发者提供了一些event和method用以提供页面交互而不需要借助第三方的JavaScript库。

总的来说桌面浏览器都支持但是移动端基本不支持,因此这些API在现代桌面浏览器上都可以正常运行。不过IE是个奇葩。

Chrome,Firefox,Safari和Opera的桌面端都有着令人惊叹的全面支持。不过IE的支持情况就取决于其版本了。比如:

  • IE7,IE8,IE9不支持dataTransfer.files或者.type对象。这意味着直到IE9都无法让用户从桌面拖动文件到网页。
  • 有限制的支持dataTransfer.setData/getData。在使用中,当我们拖动元素时,我们需要在drag中保存数据以供drop使用。在别的浏览器中可以保存为多种数据类型,包括自定义数据类型。但是IE只支持Text或者Url类型,这意味着限制了你处理数据的方式。
  • 任何版本的IE或Edge都不支持dataTransfer.setDragImage()方法。基本上没有办法设置一个自定义可拖动图片或元素。你只能使用浏览器默认的(大部分是一个半透明的图片副本)。

至于移动端,目前还没有对这些API有用的支持(2015.10)。这或许是因为移动端浏览器承载交互动作的方式。IE11是唯一会支持的移动端浏览器(我擦!)

附加内容

还有很多关于Drag和Drop API详细信息的资源。其中一些讨论了可用的方法和event在特定浏览器中的矛盾或者有一些不错的例子。

从这些链接开始吧,在我初探native drag和drop时通过他们收益良多。

总结

目前为止,你应该对native Drag 和 Drop API有一个大致的了解同时清楚如何通过它们来提供一个强交互界面。你或许应该多做练习来真正理解我在这里讨论的这些东西。

即使缺少移动端的支持,桌面端浏览器还是很强大,因此这依旧是一个在新项目中使用native API的好理由。

原文链接:http://www.gbtags.com/gb/share/9631.htm

posted @ 2015-12-10 17:20  嘤嘤嘤  阅读(1048)  评论(0编辑  收藏  举报