随着网络的发展,现代人对后端新体验的要求急速提高,过去纯点选式的页面操作形式不免让人深感厌倦。为了使使用者操作形式更方便快捷,HTML5中追加了几项机能 – 拖曳,它容许使用者以滑鼠拖曳的形式来操作形式页面,这更为合乎现代人的操作形式生活习惯。事实上该机能更多的是倚赖JavaScript API的全力支持。除全力支持在应用程序外部拖曳原素外,该USB还全力支持从应用程序外部向应用程序内拖曳文档,它借助于的是操作形式系统的全力支持以及HTML5追加的除此之外两个优点 – File。上面他们就一同上看HTML5的拖曳如何使用。
拖曳的基本概念
在HTML5中,拖曳是由一连串与拖曳有关的该事件共同组成的,如下表所示:
该事件名产生该事件的原素该事件表明dragstart被拖曳的原素拖曳已经开始drag被拖曳的原素拖曳操作过程中dragover拖曳时滑鼠历经的原素被拖曳原素在现阶段原素下方终端dragenter拖曳时滑鼠历经的原素被拖曳原素步入现阶段原素地区dragleave拖曳时滑鼠历经的原素被拖曳原素返回现阶段原素地区drop分页的最终目标原素有原素被分页到了现阶段原素中dragend分页的第一类原素拖曳完结
前述该事件的促发业务流程为:
当滑鼠点选两个原素并终端,这会在该原素上促发dragstart和drag该事件(如果原素不是镜像或相片,则须要增设draggable=“true”,不然不容许拖曳)。当被拖曳原素步入某一原素的地区时,会促发该地区原素的dragenter该事件。当被拖曳原素在某一原素下方移动时,会促发该地区原素的dragover该事件。当被拖曳原素返回某一原素时,会促发该地区原素的dragleave该事件。释放出来滑鼠时,会促发最终目标原素的drop该事件,同时能促发被拖曳原素的dragend该事件。那些该事件形成了一次拖曳完备的开发周期,拖曳操作过程中的所有犯罪行为都应该在那些该事件中表述。拖曳最核心理念的业务流程就是:在拖曳已经开始时,将他们须要传达的数据载入两个拖曳该事件第一类内;当释放出来滑鼠时,由最终目标原素得到这个该事件
现在他们以两个最基本的原生植物拖曳为例,来传授拖曳的大体业务流程。该范例源自W3School(这里是截屏,如需新体验效用,请点选页面实例原生植物拖曳):
这里是两个div,其中左边的div里含有一张相片,现在他们希望实现将相片自由在两个div内拖曳。上面是页面的HTML结构(这里省略了css样式代码):
<div id=“div1”> <img id=“drag1” src=“/i/eg_dragdrop_w3school.gif”/> </div> <div id=“div2”> </div>上面暂时省略了拖曳有关的该事件,他们将通过一步步为原素添加该事件,来传授那些该事件具体的含义和用法。
首先第一步,他们须要为相片(img原素)增设draggable=“true”(事实上img和a原素默认就是可拖曳的,不须要增设该参数。这里为了传授原理,他们暂且把相片当做两个普通原素看待)。于是img标签就变成了上面的样子:
<img id=“drag1” src=“/i/eg_dragdrop_w3school.gif” draggable=“true”/>现在img就成了两个可拖曳的原素。当你用滑鼠在该原素上点选并终端时,滑鼠的下方就会出现一张浅色的相片跟随滑鼠终端,这是应用程序的默认犯罪行为,它表示现阶段你正在拖曳该相片。
虽然应用程序为滑鼠下方添加了一张相片,让使用者在视觉上认为相片已经跟着滑鼠终端了,但事实并不是这样。如果只是添加这两个属性,当你把相片拖曳到右侧容器并释放出来滑鼠后,你会发现什么都没有发生 – 相片仍然在原来的位置。这表明拖曳并不是只开启原素的拖曳机能就可以。想要实现拖曳机能,最重要的是倚赖两个该事件第一类,这个该事件第一类可以看做两个数据载体,而上面讲到的7个拖曳该事件就是容许他们在不同阶段操作形式这个该事件第一类。
他们上看这个该事件第一类在拖曳的操作过程中的具体犯罪行为。
上面他们说到,dragstart在被拖曳原素上促发,于是他们可以为被拖曳原素(也就是img相片)注册两个回调函数,它负责在拖曳已经开始时向该事件第一类载入数据:
<img id=“drag1” src=“/i/eg_dragdrop_w3school.gif” draggable=“true” ondragstart=“drag(event)”/> <script> function drag(event){ event.dataTransfer.setData(“Text”,event.target.id); } </script>现在img上注册了两个dragstart回调函数。一旦他们对该相片执行了拖曳,应用程序就会封装两个拖曳第一类(他们记为event),并促发该原素的dragstart该事件,并将该第一类传入他们的回调函数。得到这个第一类后,他们在回调函数内只表述了一行代码,就是将被拖曳原素的id保存在该第一类的dataTransfer属性内。由于拖曳该事件是在相片原素上促发的,所以event.target就是这个相片原素,此时dataTransfer内保存的就是img的id:“drag1”。setData的第两个参数“Text”表示现阶段存储的数据类型是文本(或者说是普通的字符串)。如果你想看一下这个该事件第一类是什么样的,它大概长这样:
OK,现在让他们跳过这个令人眼花的第一类。他们只须要知道这个第一类里存储了与本次拖曳有关的所有参数,而他们想要传达的数据也已经写在了它的dataTransfer属性里。
在默认情况下,所有的DOM原素都不接受释放出来犯罪行为。从视觉效用上上看,当你拖曳该相片到某块空白地区时,你可能会发现滑鼠变成了禁用图标(一般为两个圆圈带两个斜线),这表示现阶段地区不容许释放出来拖曳原素。他们可以通过该事件第一类的preventDefault()方法来禁止这种默认犯罪行为。比如他们现在想让上面范例中右侧的那个div容许释放出来被拖曳原素,他们就可以给它注册两个ondragover(滑鼠拖曳时在现阶段原素下方终端会促发该该事件)回调函数,在该函数内取消应用程序的默认犯罪行为。代码如下表所示:
<div id=“div2” ondragover=“allowDrop(event)”> </div> <script> function allowDrop(event){ event.preventDefault(); }</script>取消了应用程序的默认犯罪行为后,当滑鼠拖曳相片终端到右侧的div时,滑鼠就会变成可释放出来的图标(具体图标因应用程序而异),它表示现阶段地区接受释放出来。对于上面的代码,如果你尝试拖曳,你会发现虽然从视觉上已经可拖曳了,但是一旦滑鼠释放出来,仍然什么都没有发生,相片并没有按他们所想被放置到右侧的容器中。
这是为什么呢?
因为应用程序没有为他们释放出来滑鼠的该事件表述任何默认的犯罪行为,他们想做什么事必须自己手动表述。那么应用程序为什么不为他们提供自动终端DOM原素的默认犯罪行为呢?原因也很简单:为了避免歧义。他们知道,HTML是一种嵌套的结构,假如有上面两个嵌套的div:
当你在外部的div内释放出来滑鼠时,这个该事件不光会被外部的div原素捕获,还会被外部的div捕获(包括document原素也会捕获到这个该事件),那么应用程序把被拖曳原素添加到哪个原素上呢?应用程序无法做出选择,因此无法为开发者提供默认的犯罪行为(当然可能还有其他原因,但仅这两个原因就已经很充分了)。
但是这个问题对开发者来说就不存在任何歧义,因为开发者可以选择为哪个原素绑定回调来处理这个该事件(当然也可以都绑定,它们都会得到执行)。回到上面的范例,现在他们希望把相片分页到右侧div时终端相片,于是他们给右侧的div绑定两个ondrop该事件来监听滑鼠的释放出来该事件。代码如下表所示:
<div id=“div2” ondrop=“drop(event)” ondragover=“allowDrop(event)”></div> <script> function drop(event){ event.preventDefault();var data=event.dataTransfer.getData(“Text”); event.target.appendChild(getElementById(data)); }</script>他们给右侧div绑定了ondrop该事件,当滑鼠拖曳相片在该地区释放出来时就会执行该回调函数,应用程序会把拖曳已经开始时生成的该事件第一类作为参数传达进来(还记得吗?他们在这个第一类的dataTransfer属性里记录了被拖曳相片原素的id,现在要派上用场了)。
首先第一步,仍然是用preventDefault()禁止应用程序的默认犯罪行为(他们在dragover里只是保证滑鼠在终端时不出现丑陋的禁用图标,但是滑鼠释放出来时仍然会出现,虽然只有一瞬间,但这非常影响使用者新体验)。
第二步,取出他们在datatTransfer里载入的相片原素的id。
第三步,用原生植物选择器从DOM树中找到这个原素,使用appendChild方法添加到现阶段原素(此时的event.target指的是右侧的容器,因为该该事件是在右侧容器上促发的)上。
所以他们真正执行的操作形式无非就是把相片原素查出来,用原生植物的DOM方法添加到右侧容器中。但是被拖曳的相片和最终目标容器本身是相互独立的,只有借助于两个该事件第一类,最终目标容器才知道到底是哪个原素须要被添加进来。而这个该事件的dataTransfer属性就是数据传达的载体。
经过上面的修改,这个代码就可以实现把相片从左侧div拖曳到右侧div了。为了能够实现两个容器的相互拖曳,他们须要为左侧容器也写上同样的监听该事件,这样两个div就都具备了放置相片原素的能力,也就是W3School实例中的效用。两个最基本的拖曳也就实现了。
换个角度看拖曳
(原创声明:如需引用该部分内容,请注明出处)
从更高的角度来说,拖曳是应用程序为开发者封装的两个消息通道。这个通道的起点是被拖曳的原素,终点
使用者将滑鼠放在两个原素上,按下并拖曳的犯罪行为将在该原素上开启这个消息通道。应用程序会生成两个用于描述该拖曳犯罪行为的该事件第一类,他们可以通过该第一类载入自己的数据,也可以增设与本次拖曳有关的参数(比如终端时滑鼠的样式、容许的拖曳类型等),然后该第一类将在消息通道内传达。
应用程序掠过时变为可放置的样式,就可以通过event.preventDefault()来实现,就像他们上面做的那样)。让他们从新的角度重新上看应用程序为开发者提供的7个原生植物该事件:
该事件名产生该事件的原素角色该事件表明第一类
从表格中可以看到,被拖曳的原素(通道的起点)可以在拖曳的已经开始和完结,以及拖曳的整个操作过程(通常每隔350毫秒促发一次)中监听该该事件。滑鼠历经的原素只能在滑鼠从该原素下方历经时监听到拖曳该事件(这个操作过程又细分为步入、终端和返回)。而最终目标原素只能在滑鼠释放出来时才能监听到该该事件(因为在使用者释放出来滑鼠之前,他们无法知道最终目标原素是谁)。
现在是不是对拖曳又有了新的认识?
既然拖曳只是建立两个消息通道,那么他们可以传递的消息又何止原素的id呢?事实上该第一类全力支持载入四种数据类型:
“text/plain”:或简写为”text”。纯文本,也就是字符串。“text/html”:HTML格式的数据。“text/xml”:xml格式的数据。“text/url-list”:或简写为“url”。url列表。事实上第一种数据类型就可以满足大多数情况下的需求。对于非字符串类型的数据,只须要压缩成字符串,最后再解析为原数据结构即可(如json数据可以用JSON.stringify压缩成字符串,再用JSON.parse解析为第一类)。
上面他们只是传达了被拖曳原素的id,事实上与该原素有关的任何参数,甚至与该原素无关的数据(只要他们认为它对本次拖曳有用),都可以载入通道。而且释放出来滑鼠时也不一定要执行appendChild来添加原素,他们可以在释放出来滑鼠时做任何他们想做的事(如弹出两个提示框,或者根据传过来的参数生成任意的DOM结构,甚至把被拖曳的原素添加到页面的任何地方(只要你认为须要这样做,应用程序都是容许的,哪怕使用者觉得很奇怪))。
上面我将自己写两个实例,来表明拖曳的灵活性(代码在后面可以找到,可以直接保存为两个HTML文档双击运行)。
该范例中,他们把上面的两个可拖曳按钮的textContent(即:“可拖曳原素”这几个字)载入该事件第一类。然后为第两个div表述的拖曳犯罪行为是添加到外部的两个ul中,为第二个表述的犯罪行为是使用alert弹出提示框,为第三个表述的犯罪行为是将其显示在第两个div内,同时在字符串前面拼上“源自第三个div的”这几个字。
现在当他们向第两个div内拖曳时,列表就会多出几项,文字内容为“可拖曳原素”。而向第二个div内拖曳时,就会出现如图所示的页面提示信息。向第三个div内拖曳时,他们看到在第两个div内列表的最后面多了几项“源自第三个div的可拖曳原素”。js代码如下表所示:
<!DOCTYPE html> <html lang=“en”> <head> <meta charset=“UTF-8”> <title>HTML5原生植物拖曳</title> <style> .des{ width:300px; height:200px; float: left; border: 1px solid #e6e6e6; } p{ color: #a6a6a6; font-size: 12px; } </style> </head> <body> <button id=“src” draggable=“true” class=“nav”>可拖曳原素</button> <br/><br/> <div id=“des1” class=“des”> <p>放进该地区会显示为列表<p/> <ul id=“container”> </ul> </div> <div id=“des2” class=“des”> <p>放进该地区会得到一条提示<p/> </div> <div id=“des3” class=“des”> <p>放进该地区会输出在第两个div内<p/> </div> </body> <script> var src = getElementById(“src”); var des1 = getElementById(“des1”); var des2 = getElementById(“des2”); var des3 = getElementById(“des3”); src.addEventListener(“dragstart”, function(e){ vardt = e.dataTransfer; dt.effectAllowed =all; dt.setData(“text/plain”, e.target.textContent); }); des1.addEventListener(“drop”, function(e){ var dt = e.dataTransfer; var text = dt.getData(“text/plain”); varcontainer = getElementById(“container”); var li = createElement(“li”); li.textContent = text; container.appendChild(li); e.preventDefault(); e.stopPropagation(); },false); des2.addEventListener(“drop”, function(e){ var dt = e.dataTransfer; var text = dt.getData(“text/plain”); ; e.preventDefault(); e.stopPropagation(); }, false); des3.addEventListener(“drop”, function(e){ var dt = e.dataTransfer; var text = dt.getData(“text/plain”); text =“源自第三个div的” + text; var container = getElementById(“container”); var li = createElement(“li”); li.textContent = text; container.appendChild(li); e.preventDefault(); e.stopPropagation(); },false); des1.ondragover = function(e){e.preventDefault();} des1.ondrop =function(e){e.preventDefault();} des2.ondragover = function(e){e.preventDefault();} des2.ondrop =function(e){e.preventDefault();} des3.ondragover = function(e){e.preventDefault();} des3.ondrop = function(e){e.preventDefault();} </script> </html>虽然是很简单的实例,但是我想已经可以证明拖曳的灵活性。除此之外这里只是在释放出来时有不同的犯罪行为,他们还可以对不同的原素在拖曳已经开始时向该事件第一类载入任意的内容,这样组合起来拖曳就会变得相当灵活。
上面他们介绍两个将element-ui的el-tree上的节点拖曳到外部的范例,它更能表明原生植物拖曳的强大之处。
el-tree中节点拖曳的扩展
el-tree是Vue的element-ui中的树组件,该组件提供了较为强大的拖曳机能,两个最基础的可拖曳树只须要写成上面这样即可(源自element-ui官网):
<el-tree :data=“data” node-key=“id” default-expand-all @node-drag-start=“handleDragStart” @node-drag-enter=“handleDragEnter” @node-drag-leave=“handleDragLeave” @node-drag-over=“handleDragOver” @node-drag-end=“handleDragEnd” @node-drop=“handleDrop”draggable :allow-drop=“allowDrop” :allow-drag=“allowDrag”> </el-tree>这里的该事件监听器就对应他们上面讲到的原生植物该事件,它们对每个节点都是生效的。draggable属性表示开启树的拖曳机能,这样树的每个节点都会被添加draggable属性。
默认情况下,树上的节点只全力支持在树的外部拖曳。也就是说,你无法把树上的节点拖曳到树的外面。但是这又是两个非常常见的需求(github的issue中说el-tree具备这个能力,但是并没有找到有关实例)。作为后端开发者,如果框架有一定的局限性,原生植物技术将是他们最强大的武器。上面我将简单介绍我是如何将树上的节点拖曳到树的外部的,希望对感兴趣的同学有所启发。
假设他们现在有两个容器,他们希望可以把树上的某一节点拖曳到这个容器里形成两个列表,这个列表暂时只保留原树节点的文本内容和id(因具体需求而异)。他们继续使用上面的树作为拖曳源,并给出上面两个div作为容器:
<div class=“menu-list” @drop=“handleTargetDrop” @dragover=“handleTargetDragOver”> <ul> <li v-for=“item in menus” :key=“item.id> <span>{{item.name}}</span> </li> </ul> </div>由于这是在Vue中,他们不须要直接操作形式DOM,只须要修改该ul对应的数据即可。在拖曳已经开始之前,menus值为空,所以该容器内不会显示任何内容。
现在他们先来处理el-tree。由于他们不须要改变树结构,因此须要屏蔽树自身的drop犯罪行为,这可以很容易通过增设绑定的allow-drop来实现,同时须要增设allow-drag使节点可拖曳:
allowDrop(draggingNode, dropNode, type) { return false; }, allowDrag(draggingNode) { return true; },好的,现在树上的节点都无法在树上终端了,并且都是可拖曳的。接下来要处理向外部拖曳的犯罪行为了。他们须要表述节点的node-drag-start该事件,它与原生植物该事件的dragstart对应,是框架向他们提供的USB。他们可以在该该事件的回调函数内将他们须要传达的数据封装进去,为了简单,他们直接传达整个节点的data即可。如下表所示:
handleDragStart(node, ev) { letdt = ev.dataTransfer; ev.dataTransfer.effectAllowed =copy; dt.setData(“text/plain”, JSON.stringify(node.data)); },现在他们把树上被拖曳的那个节点的data压缩成json字符串写进了该事件第一类的dataTransfer里。至此,树节点已经可以向外提供节点数据了。
下一步就是要处理他们的容器了。首先,他们要取消应用程序阻止拖曳的默认犯罪行为,为了使用者新体验,他们在dragover和drop中同时阻止该犯罪行为(drop的他们后面可以看到)。
handleTargetDragOver(e){e.preventDefault(); },上面就是要处理drop该事件,他们须要在滑鼠释放出来时修改容器的列表所对应的数据menus(从这里就可以看出MVVM的设计理念,他们的视线永远放在如何操作形式数据上,而不会想着如何操作形式DOM,因为框架会在数据变化时自动操作形式DOM)。事实上这相当简单:
handleTargetDrop(e){ let data = e.dataTransfer; letcontent =JSON.parse(data.getData(“text/plain”)); this.menus.push({id: content.id, name: content.name}); e.preventDefault();//通常不须要阻止冒泡,但是当出现容器嵌套时最好这么做 //它可以防止节点被添加到数组中两次 e.stopPropagation(); }他们看到,只须要非常简单的代码,就可以将树上的节点拖曳到外部容器了,这再一次证明了原生植物拖曳的灵活性和强大。
总结
本文并不是一篇详细介绍原生植物拖曳细节的文章。事实上拖曳该事件中有很多的参数都可以增设,比如你可以增设现阶段拖曳只能复制,或者修改滑鼠终端时跟随滑鼠终端的相片,你还可以增设当原素步入某一地区时,底部的原素产生一定的动态效用,这样会带来相当高级的使用者新体验。
此外,在该事件第一类的dataTransfer中还包含两个有用的属上传到服务器等(如果你使用的是Chrome,并且征得了使用者同意,甚至可以修改那些文档,这倚赖fileWriterUSB,但由于安全问题,该USB的全力支持性不是很好)。除此之外,单凭拖曳甚至可以写出一些有趣的HTML5页面游戏,而这完全取决于你的创造能力。
本文最重要的目的不是展示该技术可以被使用得多么神奇,而是希望探究它的基本概念,为以后的使用打下良好的基础。希望对不了解原生植物拖曳的同学有所帮助。