平常我们写网页拖拽的时候,一般都是通过mousedown+mousemove+mouseup这种方式来模拟出拖拽的效果的,今天看书的时候,偶然看到原来浏览器早就支持了原生的拖拽事件,那就是dragstart+drag+dragend这一套东西。
不难理解,看字面意思就知道这是开始拖拽+拖拽+完成拖拽分别触发的事件。
用这种原生的拖拽方式未必会比监听鼠标的方式好(特别是扩展性不够强),但至少我感觉相同的一段拖拽效果,用dragstart方式会要少写一些代码(省了碰撞检测不是)。
我们来先上一段代码,是监听鼠标事件模拟出的网页拖拽效果。当然你与我的方式未必相同,但想来应该大同小异。
1 window.onload =function(){ 2 3 var oBody = document.body; 4 var oShow = document.getElementById('show'); 5 var aImg = document.getElementById('menu').getElementsByTagName('img'); 6 var moveable = false; 7 8 for (var i=0; i<aImg.length; i++) { 9 10 aImg[i].onmousedown = function(ev){ 11 var ev = ev || window.event; 12 var This = this; 13 14 //alert(document.documentElement.scrollTop) 15 16 var clonImg = document.createElement('img'); 17 clonImg.id = 'clonimg'; 18 clonImg.src= This.getAttribute('src'); 19 clonImg.style.position = 'absolute'; 20 clonImg.style.left = ev.clientX +'px'; 21 clonImg.style.top = ev.clientY +'px'; 22 clonImg.style.width = 150+'px'; 23 clonImg.style.opacity = 0.5; 24 clonImg.style.filter = 'alpha(opacity=50)'; 25 26 oBody.appendChild(clonImg); 27 28 //这是解决ie7下的一个兼容bug--来自妙味 29 if(This.setCapture){ 30 This.setCapture(); 31 } 32 33 document.onmousemove = function(ev){ 34 var ev = ev || window.event; 35 clonImg.style.left = ev.clientX+ 'px'; 36 clonImg.style.top = ev.clientY+ 'px'; 37 }; 38 39 document.onmouseup = function(){ 40 41 document.onmousemove = null; 42 document.onmouseup = null; 43 if(clonImg.releaseCapture){ 44 clonImg.releaseCapture(); 45 } 46 if(runInto(clonImg,oShow)){ 47 clonImg.style.opacity = 1; 48 clonImg.style.filter = 'alpha(opacity=100)'; 49 } 50 else{ 51 oBody.removeChild(clonImg); 52 } 53 }; 54 ev.stopPropagation(); 55 return false; 56 57 }; 58 59 } 60 61 } 62 63 function runInto(obj1,obj2){//碰撞检测 64 65 var L1 = obj1.offsetLeft; 66 var T1 = obj1.offsetTop; 67 var R1 = L1 + obj1.offsetWidth; 68 var B1 = T1 + obj1.offsetHeight; 69 70 var L2 = obj2.offsetLeft; 71 var R2 = L2 + obj2.offsetWidth; 72 var T2 = obj2.offsetTop; 73 var B2 = T2 + obj2.offsetHeight; 74 75 if( R1<=L2 || L1>=R2 || T1>=B2 || B1<=T2 ){ 76 return false; 77 } 78 else{ 79 return true; 80 } 81 };
顺便附上我的网页结构和css代码还有实际效果图。
1 <div id="main"> 2 <div id="menu"> 3 <a href="#"><img src="images/a.jpg" alt=""></a> 4 <a href="#"><img src="images/b.jpg" alt=""></a> 5 <a href="#"><img src="images/c.jpg" alt=""></a> 6 </div> 7 <div id="show"></div> 8 </div>
*{ padding: 0; margin: 0;} #main{position: relative;} #menu{ position: absolute; left: 30px; top: 30px;} #menu img{ display: block; width: 150px; margin: 0px 0 20px;} #show{ position: absolute; left: 400px; top: 30px; width: 800px; height: 500px; border: 1px solid #f00;}
同样的html结构,我们来看看dragstart方式实现拖拽的代码。
首先要说明的一点是浏览器本身拖拽事件是dragstart没错,但是我们接受拖拽的元素却是另一种方式。
我们在html中对于普通的元素只能拖拽并不能接收拖拽元素,大多时候默认只有input才能够接收拖拽元素,这时候我们需要做一些处理才能够让普通的元素接收拖拽元素。
我是在高程中学到的方式,看书总是能够让我们获得未知的知识。
我们只需要清除目标元素的dragenter(进入目标元素时触发)、dragover(在目标元素中移动会持续触发)、drop(拖拽完成并且拖拽元素处于目标元素中时会触发)三个事件就能够让普通的html元素变成可以接受拖拽元素的元素。
书上提到最开始是由ie4引入拖拽功能(dragstart等),不过当时只能支持input接收拖拽元素,不过ie5以上开始支持所有元素。Firefox、chrome等也相继提供支持。所以兼容问题应该不会很大。我这边测试过ie7+能够正常拖拽无误。
拖拽之后是通过dataTransfer对象存储拖拽的对象,HTML5可以提供更加复杂对象存储,不过之前的浏览器版本可能只支持图片或者链接的URL和文字,因此我们如果要进行一些复杂对象的拖拽,可能需要在dragstart时手动进行存储,之后在手动读取。
具体的实现方式来看下我的代码。部分知识已在注释中提到,可方便大家阅读。
1 window.onload =function(){ 2 3 var oShow = document.getElementById("show"); 4 5 //方法取自高程第三版482-483页 6 //改变普通元素为拖拽目标元素,阻止默认行为,让show变为可接受拖拽的元素 7 //dragenter拖拽元素到达目标元素时触发的事件 8 addEvent(oShow,"dragenter",function(event){ 9 eventPreventdefault(event); 10 }) 11 //dragover拖拽元素在目标元素中移动时触发 12 addEvent(oShow,"dragover",function(event){ 13 eventPreventdefault(event); 14 }) 15 //鼠标松开拖拽元素并且拖拽元素处于目标元素之中,就会执行drop事件 16 addEvent(oShow,"drop",function(event){ 17 18 //dataTransfer对象用于存储拖拽的数据(html5之前只支持url和text2种方式,因此如果要兼容的话,可以在dragstart时将复杂对象存储。) 19 var dataTransfer = event.dataTransfer; 20 var drawImgUrl = dataTransfer.getData("url") || dataTransfer.getData("text/uri-list"); 21 var clonImg = document.createElement('img'); 22 clonImg.src= drawImgUrl; 23 clonImg.style.width = 150+'px'; 24 clonImg.style.position = 'absolute'; 25 clonImg.style.left = (event.clientX - oShow.offsetLeft) + 'px'; 26 clonImg.style.top = (event.clientY - oShow.offsetTop) + 'px'; 27 oShow.appendChild(clonImg) 28 eventPreventdefault(event); 29 }) 30 } 31 32 //addEvent为元素添加事件 33 function addEvent(obj, sEvent, fn){ 34 //attachEvent支持IE下添加事件,格式obj.attachEvent(event,function);event带on,如onclick 35 if(obj.attachEvent){ 36 obj.attachEvent('on'+sEvent, fn); 37 } 38 else{ 39 //addEventListener支持Firefox、Chrome等W3C浏览器,格式addEventListener(event,obj,false),event不带on,如click,false为定值不用更改 40 obj.addEventListener(sEvent, fn, false); 41 } 42 }; 43 44 //取消默认行为 45 function eventPreventdefault(event){ 46 if(event && event.preventDefault){ 47 event.preventDefault(); 48 }else{ 49 window.event.returnValue = false;//低版本ie不支持prevendefault()只能通过returnValue取消默认行为 50 } 51 }
这是通过dargstart方式实现的效果,基本相似,由于少了碰撞检测的部分,所以代码应该会相对少一些,当然如果需要进行复杂的包含检测,依然的在drop中进行检测。
两种实现方式孰优孰劣我不知道,我并不擅长性能方面的测试,但我想原生的方式应该会更加优秀一些。如果有人也有这方面的尝试,可以相互交流。