js拖拽效果 javascript实现将元素拖拽如某容器效果demo
拖拽效果demo以及文档说明
Demo效果
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>无标题文档</title> <script type="text/javascript"> /*************************************************c插件定义部分******************************************************/ var Drag = (function () { var opts={container:"",drag_items:"",state:"outer" ,setContainerStyle:true ,moveInto:function(){},moveOut:function(){},endInto:function(){}},//默认参数,全局的 state:inner outer center 标示拖拽对象是碰触目标触发还是完全进入目标再触发 curDrag, //以下为私有方法 getPosition=function(elem){ if ( !elem ) return {left:0, top:0}; var top = 0, left = 0; if ( "getBoundingClientRect" in document.documentElement ){ //jquery方法 var box = elem.getBoundingClientRect(), doc = elem.ownerDocument, body = doc.body, docElem = doc.documentElement, clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0, top = box.top + (self.pageYOffset || docElem && docElem.scrollTop || body.scrollTop ) - clientTop, left = box.left + (self.pageXOffset || docElem && docElem.scrollLeft || body.scrollLeft) - clientLeft; }else{ do{ top += elem.offsetTop || 0; left += elem.offsetLeft || 0; elem = elem.offsetParent; } while (elem); } return {left:left, top:top}; }, isJoinToBox=function(curDrag){ var extendX, extendY, drag_left = parseInt(curDrag.style.left), drag_top = parseInt(curDrag.style.top), box_position = getPosition(opts.container), box_top = box_position.top, box_left = box_position.left, box_bottom = opts.container.offsetHeight + box_top, box_right = opts.container.offsetWidth + box_left; switch(opts.state) { case "outer": extendX = curDrag.offsetWidth; extendY = curDrag.offsetHeight; break; case "inner": extendX=0; extendY=0; break; case "center": extendX = parseInt(curDrag.offsetWidth/2); extendY = parseInt(curDrag.offsetHeight/2); break; default: extendX = curDrag.offsetWidth; extendY = curDrag.offsetHeight; } return ((drag_left >= box_left-extendX && drag_left <= box_right) && (drag_top >= box_top-extendY && drag_top <= box_bottom)); }, start=function(e){ window.getSelection().removeAllRanges(); var oldNode = e.target, newNode, top = getPosition(oldNode).top, left = getPosition(oldNode).left; if("true" === oldNode.getAttribute("drag")) { newNode = oldNode.cloneNode(true); newNode.style.cssText = "z-index:1;background:red;position:absolute; top:"+top+"px;left:"+left+"px"; oldNode.parentNode.appendChild(newNode); curDrag = newNode; } }, move=function(e){ window.getSelection ? window.getSelection().removeAllRanges():document.selection.empty(); if(curDrag){ curDrag.style.top = e.pageY + "px"; curDrag.style.left = e.pageX + "px"; if(opts.setContainerStyle){ if(isJoinToBox(curDrag)) { opts.moveInto(); } else{ opts.moveOut(); } } } }, end=function(e){ window.getSelection ? window.getSelection().removeAllRanges():document.selection.empty(); if(curDrag){ if(isJoinToBox(curDrag)){ opts.endInto(curDrag.cloneNode(true)); } if(opts.setContainerStyle){ opts.moveOut(); } curDrag.parentNode.removeChild(curDrag); curDrag = null; } }, touchHandler=function(event){ var touches=event.changedTouches, touchObj=touches[0], type, simulatedEvent ; switch(event.type){ case "touchstart": type="mousedown"; break; case "touchmove": type="mousemove"; break; case "touchend": type="mouseup"; break; default: return; } simulatedEvent=document.createEvent("MouseEvent"); simulatedEvent.initMouseEventype(type, true, true, window, 1, first.screenX,first.screenY, first.clientX, first.clientY, false,false, false, false, 0, null); touchObj.target.dispathEvent(simulatedEvent); event.preventDefault(); }, init=function (option) { for(prop in option) opts[prop]=option[prop]; //alert(opts.container); //初始化事件 //如果是移动设备,我们就用事件模拟触发鼠标事件 opts.drag_items.addEventListener("touchstart", touchHandler, true); opts.drag_items.addEventListener("touchmove", touchHandler, true); opts.drag_items.addEventListener("touchend", touchHandler, true); //监听鼠标事件 opts.drag_items.addEventListener("mousedown", start, true); document.addEventListener("mousemove", move, true); document.addEventListener("mouseup", end, true); }; // 暴露公开的成员 return{ init: init } }()); </script> </head> <style type="text/css"> span{display:block; width:50px; height:50px; background:#dedede; border:1px solid #333; float:left} #container{margin:auto;padding:10px;background:#dedede; width:380px; height:150px; border:5px solid #777; } #container li{width:50px; height:50px; border:1px dashed #777; float:left; margin:10px 0 0 10px; list-style-type:none;} #dragItems{margin:auto; width:480px;} </style> <body style="text-align:center;-webkit-user-select:none;"> 将以下小方格拖入上面的容器内:<br /> 注: id="dragItems"下面的子元素设置 drag="true"属性的才可以拖动<br /><br /><br /> <ul id="container"> <li></li><li></li><li></li><li></li><li></li><li></li> <li></li><li></li><li></li><li></li><li></li><li></li> </ul> <br /> <br /> <br /> <br /> <div id="dragItems"><span drag="true">111</span><span drag="true">222</span><span drag="true">333</span><span drag="true">444</span><span drag="true">555</span><span drag="true">666</span><span drag="true">777</span><span drag="true">888</span><span drag="true">999</span></div> </body> </html> <script type="text/javascript"> //*******************************************************调用部分************************************************** var c=document.getElementById("container"),//需要拖入的容器id d=document.getElementById("dragItems");//被拖动的元素列表 Drag.init({ container : c, drag_items : d , state : "outer",//可以设置层outer(拖动元素外边挨着容器就触发),inner(拖动元素全部进入容器才触发),center (拖动元素一半进入容器时触发) moveInto : function(){//当元素被拖入容器内时触发(mousemove时鼠标左键按下状态) c.style.border = "5px dashed #ccc"; }, moveOut : function(){//当元素被拖出容器时触发(mousemove时鼠标左键按下状态) c.style.border = "5px solid #777"; }, endInto : function(dragEle){//当元素被拖进容器后触发(mouseup 鼠标抬起) var elems=c.getElementsByTagName("li"); for(var i=0,len=elems.length; i<len ;i++) { if(elems[i].innerHTML == "") { elems[i].appendChild(dragEle); dragEle.style.position=""; break; } if(i == len-1) alert("fulled"); } } }); </script>
JavaScript 拖放效果相关技术说明
【触发对象】
触发对象是用来触发拖放程序的,程序中通过Handle属性设置。有的时候不需要整个拖放对象都用来触发,这时就需要触发对象了。
使用了触发对象后,进行移动的还是拖放对象,只是用触发对象来触发拖放(一般的使用是把触发对象放到拖放对象里面)。
ps:触发对象的另一个用法是通过设置相同的Handle,实现一个触发对象同时拖放多个拖放对象。
【范围限制】
要设置范围限制必须先把Limit设为true。范围限制分两种,分别是固定范围和容器范围限制,主要在Move程序中设置。
原理是当比较的值超过范围时,修正left和top要设置的值使拖放对象能保持在设置的范围内。
【取消默认动作】
对选择状态的文本内容、连接和图片等进行拖放操作会触发系统的默认动作,例如ie中拖动图片鼠标会变成禁止操作状态,这样会导致这个拖放程序执行失败。
不过ie在设置了setCapture之后,通过用户界面用鼠标进行拖放操作和内容选择都会被禁止。
意思就是setCapture之后就不能对文档内容进行拖放和选择,注意这里的拖放是指系统的默认动作,例如ondragstart就不会被触发。
不过如果setCapture的参数是false的话,容器内的对象还是可以触发事件的(具体看鼠标捕获部分),所以setCapture的参数要设成true或保留默认值。
而ff的鼠标捕获没有这个功能,但可以用preventDefault来取消事件的默认动作来解决:
oEvent.preventDefault();
ps:据说使用preventDefault会出现mouseup丢失的情况,但我在ff3中测试没有发现,如果各位发现任何mouseup丢失的情况,务必告诉我啊。
【清除选择】
ie在设置setCapture之后内容选择都会被禁止,但也因此不会清除在设置之前就已经选择的内容,而且设置之后也能通过其他方式选择内容,
例如用ctrl+a来选择内容。
ps:onkeydown、onkeyup和onkeypress事件不会受到鼠标捕获影响。
而ff在mousedown时就能清除原来选择的内容,但拖动鼠标,ctrl+a时还是会继续选择内容。
不过在取消了系统默认动作之后,这样的选择并不会对拖放操作造成影响,这里设置主要还是为了更好的体验。
以前我用禁止拖放对象被选择的方法来达到目的,即ie中设置拖放对象的onselectstart返回false,在ff中设置样式MozUserSelect(css:-moz-user-select)为none。
但这种方法只能禁止拖放对象本身被选择,后来找到个更好的方法清除选择,不但不影响拖放对象的选择效果,还能对整个文档进行清除:
ie:document.selection.empty()
ff:window.getSelection().removeAllRanges()
为了防止在拖放过程中选择内容,所以把它放到Move程序中,下面是兼容的写法:
window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
这里说说ff下有一个比较奇怪的现象,在SimpleDrag中第一次拖放没有问题,但第二次拖放时鼠标就会显示禁止操作的样式(拖动失败)。
我发现如果在Start程序中阻止默认动作(preventDefault)或清除选择(window.getSelection().removeAllRanges())就能正常了。
所以我估计是在点击事件后,拖放对象已经默认设置成选择状态了(虽然看起来不是),再拖动时就触发了系统的默认拖动事件。
为了支持推断,我在html插入一段文本,然后在每一次拖放之后选择一下那段文本(间接取消拖放对象的选择),那就能正常了。
不过暂时还没找到官方说明,所以还不能下结论。