拖动效果相信很多朋友都自己写过,不管你用原生JS还是Jquery要实现起来也很简单,但是今天我想介绍的是运用HTML5标准中定义的原生拖动事件实现拖动效果。
一、背景:
其实说是HTML5标准定义,其实最早在IE4中就有拖放功能的API,只是在IE4中,网页中只有两种对象可以拖放:图像和某些文本。并且在IE4中唯一有效的放置目标是文本框。到了IE5.5,拖放功能得到了扩展,让网页中的任何元素都可以拖放。最终HTML5以IE的实力为基础制定了拖放规范。FF3.5+,Safari3+和Chrome根据规范实现了原生拖放。
二、事件:
原生拖放中涉及到的事件分为分下列两个阶段:
(1)拖动某元素时,该阶段会依次触发下列事件(该阶段的事件目标--即target或srcElement都是这个被拖动元素):
1>dragstart——鼠标移入目标元素并且按下左键触发。
2>drag——dragstart触发后移动鼠标连续触发该事件(类似mousemove事件)
3>dragend——拖动停止时触发(无论此时拖动元素在有效位置还是无效位置)。
(2)当元素被拖动到一个有效的放置目标上时,触发下列事件(该阶段的事件目标--即target或srcElement都是这个目标元素):
1>dragenter——只要有元素被拖动到放置目标上,就触发dragenter事件(类似mouseover)
2>dragover——触发dragenter后在有效目标范围内移动时连续触发该事件
3>dragleave——被拖动元素从目标范围内被拖出到目标范围外时触发
4>drop——被拖动元素被放到了目标范围内(即在有效目标范围内松口鼠标左键)
注意:这里dragleave和drop二者只能触发其一!
三、定义放置位置:
虽然所有元素都支持放置目标事件,但是这些元素默认情况下是不允许放置的,如果拖动元素经过不允许放置的元素,那无论怎样都无法触发drop事件。所 以我们必须先定义出放置的目标元素。
定义目标元素也很简单,只要阻止dragenter和dragover事件的默认行为就可以了,假设我们的目标元素为一个id为drag-area的div,我们要把它定义成我 们的目标元素,只需下面代码就可以了:
drag_area.ondragover=function(evt){ //阻止dragover的默认事件 var evt=evt || window.event; if(typeof evt.preventDefault=="function"){ evt.preventDefault(); }else{ evt.returnValue=false; } } drag_area.ondragenter=function(evt){ //阻止dragenter的默认事件 var evt=evt || window.event; if(typeof evt.preventDefault=="function"){ evt.preventDefault(); }else{ evt.returnValue=false; } }
(有小伙伴可能会问了,啊~阻止默认事件不是可以直接用return false么,干嘛这么麻烦。的确,return false可以阻止默认事件和事件传播,而且我测试过,用return false;的确也可以达到相同效果。这里主要是firefox的一个问题,我的firefox是最新版本,直接用return false没有问题,但是据 http://blog.csdn.net/goldlevi/article/details/5721348 说,稍早一些的firefox版本如果dragenter和dragover没有设置preventDefault的话不会在该元素上触发drag事件的,偷个懒,就不自己测试其他firefox了~关于FF还有一点问题,后面会提到);
四.实现
前面解释了一下自定义放置目标,这里具体的实现就不多说了(其实原理和普通实现差不多~)直接上代码:
<!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content="text/html,charset=utf-8"> <title></title> <style type="text/css"> #drag-area{ width: 500px;height:500px; border: 1px solid red; position: relative; } #my-img{ position: absolute; } </style> </head> <body> <div id="drag-area"> <img src="pity.gif" id="my-img"> </div> </body> <script type="text/javascript"> var my_img=document.getElementById("my-img"); var drag_area=document.getElementById("drag-area"); my_img.ondragstart=function(evt){ //在被拖动的元素的dragstart事件中取得坐标便宜 var evt=evt || window.event; //即代码中evt.clientX-this.offsetLeft和evt.clientY-this.offsetTop,并保存在dataTransfer对象中 evt.dataTransfer.setData("text",(evt.clientX-this.offsetLeft)+";"+(evt.clientY-this.offsetTop)); } drag_area.ondragover=function(evt){ //阻止dragover的默认事件 var evt=evt || window.event; if(typeof evt.preventDefault=="function"){ evt.preventDefault(); }else{ evt.returnValue=false; } } drag_area.ondragenter=function(evt){ //阻止dragenter的默认事件 var evt=evt || window.event; if(typeof evt.preventDefault=="function"){ evt.preventDefault(); }else{ evt.returnValue=false; } } drag_area.ondrop=function(evt){ var evt=evt || window.event; var drag_data=evt.dataTransfer.getData("Text").split(";");//从dataTransfer对象中取出数据,并将字符串分割成数组 var offset_x=drag_data[0],//取得横向偏移 offset_y=drag_data[1];//取得纵向偏移 if(typeof evt.preventDefault=="function"){ //阻止drop事件的默认行为 evt.preventDefault(); }else{ evt.returnValue=false; } my_img.style.left=(evt.clientX-offset_x)+"px";//给拖动元素的left,top赋值 my_img.style.top=(evt.clientY-offset_y)+"px"; } </script> </html>
代码限定了被拖动元素(my_img)只能在(drag_area)这个500*500的div里面拖动。代码中我们在拖放元素的dragstart事件中取得了鼠标点击时的坐标偏移evt.clientX-this.offsetLeft和evt.clientY-this.offsetTop然后将其存放在dataTransfer对象中,
然后又在目标元素的drop事件里从dataTransfer对象中取出了偏移值,用来计算拖动元素的left和top。
解释下dataTransfer这个对象。该对象是事件对象的一个属性,通常我们在利用它在被拖动元素(如代码中的my_img)dragstart事件处理程序中设置它的值,在目标元素(代码中的drag_area)drop事件(并且只能在drop事件)中读取其值,此外,浏览器在我们拖动文本(图片/链接)时,会自动将拖动的文本(url)存放在该对象中。
设置dataTransfer内容的语法如下:
event.dataTransfer.setData("数据类型","值")——这里数据类型虽然HTML定义了多种MIME类型,但是最好根据情况只使用
‘text’—保存字符串和’URL‘—保存url(ie只定义了这两种类型);
值当然就是你需要的值啦。
通过dataTransfer对象提取数据语法如下:
event.dataTransfer.getData("数据类型")——这里数据类型同setData中的数据类型。
另外dataTransfer对象还有两个属性,dropEffect和EffectAllowed。不过个人感觉很鸡肋,就不多说了,只是改了下鼠标的样式有木有啊~
好了,有兴趣的同学把图片链接换一下试试吧~该方法兼容性尚可:opera不支持没话说,在IE6+,chrome中都没问题,但是在FF中拖动图片会自动打开图片的url,而且阻止不了(代码中drop事件有阻止默认代码~测试版本ff31.0和ff32.03)=_=!根本不科学嘛~希望看到本文,有解决方法的大神留言。
总结一下,虽然我们用mousedown、mousemove、mouseup也能方便做出拖放效果,而且没有上面说的兼容性和FF中的BUG,但是如果项目不需要兼容opera,而且是拖拽选中文本的话,此方法就比较方便了(上面说过,拖拽文本浏览器会自动将选中并拖拽的文本、URL存放在dataTransfer对象中)。而且,做前端不就是折腾么。。。。