怎么用javascript进行拖拽

  Javascript的特点是dom的处理与网页效果,大多数情况我们只用到了这个语言的最简单的功能,比如制作图片轮播/网页的tab等等,这篇文章将向你展示如何在自己的网页上制作拖拽.

  有很多理由让你的网站加入拖拽功能,最简单的一个是数据重组.例如:你有一个序列的内容让用户排序,用户需要给每个条目进行输入或者用select选择,替代前面这个方法的就是拖拽.或许你的网站也需要一个用户可以拖动的导航窗口!那么这些效果都是很简单:因为你可以很容易的实现!

  网页上实现拖拽其实也不是很复杂.第一你需要知道鼠标坐标,第二你需要知道用户鼠标点击一个网页元素并实现拖拽,最后我们要实现移动这个元素。

获取鼠标移动信息

  第一我们需要获取鼠标的坐标.我们加一个用户函数到document.onmousemove就可以了:


document.onmousemove = mouseMove; 


function mouseMove(ev){ 

ev           = ev || window.event; 

var mousePos = mouseCoords(ev); 

} 


function mouseCoords(ev){ 

if(ev.pageX || ev.pageY){ 

  return {x:ev.pageX, y:ev.pageY}; 

} 

return { 

  x:ev.clientX + document.body.scrollLeft - document.body.clientLeft, 

  y:ev.clientY + document.body.scrollTop  - document.body.clientTop 

}; 

}



  你首先要声明一个evnet对象.不论何时你移动鼠标/点击/按键等等,会对应一个event的事件.在Internet Explorer里event是全局变量,会被存储在window.event里. 在firefox中,或者其他浏览器,event事件会被相应的自定义函数获取.当我们将mouseMove函数赋值于document.onmousemove,mouseMove会获取鼠标移动事件.

  (ev = ev || window.event) 这样让ev在所有浏览器下获取了event事件,在Firefox下"||window.event"将不起作用,因为ev已经有了赋值.在MSIE下ev是空的,所以ev将设置为window.event.

  因为我们在这篇文章中需要多次获取鼠标坐标,所以我们设计了mouseCoords这个函数,它只包含了一个参数,就是the event.

  我们需要运行在MSIE与Firefox为首的其他浏览器下.Firefox以event.pageX和event.pageY来代表鼠标相应于文档左上角的位置.如果你有一个500*500的窗口,而且你的鼠标在正中间,那么paegX和pageY将是250,当你将页面往下滚动500px,那么pageY将是750.此时pageX不变,还是250.

  MSIE和这个相反,MSIE将event.clientX与event.clientY来代表鼠标与ie窗口的位置,并不是文档.当我们有一个500*500的窗口,鼠标在正中间,那么clientX与clientY也是250,如果你垂直滚动窗口到任何位置,clientY仍然是250,因为相对ie窗口并没有变化.想得到正确的结果,我们必须加入scrollLeft与scrollTop这两个相对于文档鼠标位置的属性.最后,由于MSIE并没有0,0的文档起始位置,因为通常会设置2px的边框在周围,边框的宽度包含在document.body.clientLeft与clientTop这两个属性中,我们再加入这些到鼠标的位置中.

  很幸运,这样mouseCoords函数就完成了,我们不再为坐标的事操心了.


捕捉鼠标点击

  下次我们将知道鼠标何时点击与何时放开.如果我们跳过这一步,我们在做拖拽时将永远不知道鼠标移动上面时的动作,这将是恼人的与违反直觉的.

  这里有两个函数帮助我们:onmousedown与onmouseup.我们预先设置函数来接收document.onmousemove,这样看起来很象我们会获取document.onmousedown与document.onmouseup.但是当我们获取document.onmousedown时,我们同时获取了任何对象的点击属性如:text,images,tables等等.我们只想获取那些需要拖拽的属性,所以我们设置函数来获取我们需要移动的对象.

移动一个元素

  我们知道了怎么捕捉鼠标移动与点击.剩下的就是移动元素了.首先,要确定一个明确的页面位置,css样式表要用'absolute'.设置元素绝对位置意味着我们可以用样式表的.top和.left来定位,可以用相对位置来定位了.我们将鼠标的移动全部相对页面top-left,基于这点,我们可以进行下一步了.

  当我们定义item.style.position='absolute',所有的操作都是改变left坐标与top坐标,然后它移动了。

document.onmousemove = mouseMove; 

document.onmouseup   = mouseUp; 


var dragObject  = null; 

var mouseOffset = null; 


function getMouseOffset(target, ev){ 

ev = ev || window.event; 


var docPos    = getPosition(target); 

var mousePos  = mouseCoords(ev); 

return {x:mousePos.x - docPos.x, y:mousePos.y - docPos.y}; 

} 


function getPosition(e){ 

var left = 0; 

var top  = 0; 


while (e.offsetParent){ 

  left += e.offsetLeft; 

  top  += e.offsetTop; 

  e     = e.offsetParent; 

} 


left += e.offsetLeft; 

top  += e.offsetTop; 


return {x:left, y:top}; 

} 


function mouseMove(ev){ 

ev           = ev || window.event; 

var mousePos = mouseCoords(ev); 


if(dragObject){ 

  dragObject.style.position = 'absolute'; 

  dragObject.style.top      = mousePos.y - mouseOffset.y; 

  dragObject.style.left     = mousePos.x - mouseOffset.x; 


  return false; 

} 

} 

function mouseUp(){ 

dragObject = null; 

} 


function makeDraggable(item){ 

if(!item) return; 

item.onmousedown = function(ev){ 

  dragObject  = this; 

  mouseOffset = getMouseOffset(this, ev); 

  return false; 

} 

}




  你会注意到这个代码几乎是前面的全集,将前面的合在一起就实现了拖拽效果了.

  当我们点击一个item时,我们就获取了很多变量,如鼠标位置,鼠标位置自然就包含了那个item的坐标信息了.如果我们点击了一个20*20px图像的正中间,那么鼠标的相对坐标为{x:10,y:10}.当我们点击这个图像的左上角那么鼠标的相对坐标为{x:0,y:0}.当我们点击时,我们用这个方法取得一些鼠标与图片校对的信息.如果我们不能加载页面item,那么信息将是document信息,会忽略了点击的item信息.

  mouseOffset函数使用了另一个函数getPosition.getPosition的作用是返回item相对页面左上角的坐标,如果我们尝试获取item.offsetLeft或者item.style.left,那么我们将取得item相对与父级的位置,不是整个document.所有的脚本我们都是相对整个document,这样会更好一些.

  为了完成getPosition任务,必须循环取得item的父级,我们将加载内容到item的左/上的位置.我们需要管理想要的top与left列表.

  自从定义了mousemove这个函数,mouseMove就会一直运行.第一我们确定item的style.position为absolute,第二我们移动item到前面定义好的位置.当mouse点击被释放,dragObject被设置为null,mouseMove将不在做任何事.

Dropping an Item

  前面的例子目的很简单,就是拖拽item到我们希望到的地方.我们经常还有其他目的如删除item,比如我们可以将item拖到垃圾桶里,或者其他页面定义的位置.

  很不幸,我们有一个很大的难题,当我们拖拽,item会在鼠标之下,比如mouseove,mousedown,mouseup或者其他mouse action.如果我们拖拽一个item到垃圾桶上,鼠标信息还在item上,不在垃圾桶上.

  怎么解决这个问题呢?有几个方法可以来解决.第一,这是以前比较推荐的,我们在移动鼠标时item会跟随鼠标,并占用了mouseover/mousemove等鼠标事件,我们不这样做,只是让item跟随着鼠标,并不占用mouseover等鼠标事件,这样会解决问题,但是这样并不好看,我们还是希望item能直接跟在mouse下.

  另一个选择是不做item的拖拽.你可以改变鼠标指针来显示需要拖拽的item,然后放在鼠标释放的位置.这个解决方案,也是因为美学原因不予接受.

  最后的解决方案是,我们并不去除拖拽效果.这种方法比前两种繁杂许多,我们需要定义我们需要释放目标的列表,当鼠标释放时,手工去检查释放的位置是否是在目标列表位置上,如果在,说明是释放在目标位置上了.


/* 

All code from the previous example is needed with the exception 

of the mouseUp function which is replaced below 

*/ 


var dropTargets = []; 


function addDropTarget(dropTarget){ 

dropTargets.push(dropTarget); 

} 


function mouseUp(ev){ 

ev           = ev || window.event; 

var mousePos = mouseCoords(ev); 


for(var i=0; i<dropTargets.length; i++){ 

  var curTarget  = dropTargets[i]; 

  var targPos    = getPosition(curTarget); 

  var targWidth  = parseInt(curTarget.offsetWidth); 

  var targHeight = parseInt(curTarget.offsetHeight); 


  if( 

   (mousePos.x > targPos.x)                && 


   (mousePos.x < (targPos.x + targWidth))  && 

   (mousePos.y > targPos.y)                && 

   (mousePos.y < (targPos.y + targHeight))){ 

    // dragObject was dropped onto curTarget! 

  } 

} 


dragObject   = null; 

}



  鼠标释放时会去取是否有drop属性,如果存在,同时鼠标指针还在drop的范围内,执行drop操作.我们检查鼠标指针位置是否在目标范围是用(mousePos.x>targetPos.x),而且还要符合条件(mousePos.x<(targPos.x + targWidth)).如果所有的条件符合,说明指针确实在范围内,可以执行drop指令了.


Pulling It All Together

  最后我们拥有了所有的drag/drop的脚本片断!下一个事情是我们将创建一个DOM处理.如果你不是很熟悉,请先阅读我的JavaScript Primer on DOM Manipulation.

  下面的代码将创建container(容器),而且使任何一个需要drag/drop的item变成一个容器的item.代码在这个文章第二个demo的后面,它可以用户记录一个list(列表),定为一个导航窗口在左边或者右边,或者更多的函数你可以想到的.

  下一步我们将通过"假代码"让reader看到真代码,下面为推荐:

  1、当document第一次载入时,创建dragHelper DIV.dragHelper将给移动的item加阴影.真实的item没有被dragged,只是用了insertBefor和appendChild来移动了,我们隐藏了dragHelper

  2、有了mouseDown与mouseUp函数.所有的操作会对应到当到iMouseDown的状态中,只有当mouse左键为按下时iMouseDown才为真,否则为假.

  3、我们创建了全局变量DragDrops与全局函数CreateDragContainer.DragDrops包含了一系列相对彼此的容器.任何参数(containers)将通过CreatedcragContainer进行重组与序列化,这样可以自由的移动.CreateDragContainer函数也将item进行绑定与设置属性.

  4、现在我们的代码知道每个item的加入,当我们移动处mouseMove,mouseMove函数首先会设置变量target,鼠标移动在上面的item,如果这个item在容器中(checked with getAttribute):

  运行一小段代码来改变目标的样式.创造rollover效果
  检查鼠标是否没有放开,如果没有
  设置curTarget代表当前item
  记录item的当前位置,如果需要的话,我们可以将它返回
  克隆当前的item到dragHelper中,我们可以移动带阴影效果的item.
  item拷贝到dragHelper后,原有的item还在鼠标指针下,我们必须删除掉dragObj,这样脚本起作用,dragObj被包含在一个容器中.
  抓取容器中所有的item当前坐标,高度/宽度,这样只需要记录一次,当item被drag时,每随mouse移动,每移钟就会记录成千上万次.
  如果没有,不需要做任何事,因为这不是一个需要移动的item
  5、检查curTarget,它应该包含一个被移动的item,如果存在,进行下面操作:

  开始移动带有阴影的item,这个item就是前文所创建的
  检查每个当前容器中的container,是否鼠标已经移动到这些范围内了
  我们检查看一下正在拖动的item是属于哪个container
  放置item在一个container的某一个item之前,或者整个container之后确认item是可见的
  如果鼠标不在container中,确认item是不可见了.
  6、剩下的事就是捕捉mouseUp的事件了


另外一个例子:

<html> 

<head> 

<meta http-equiv="Content-Type" content="text/html; charset=gb2312"> 

<title>鼠标拖动元素</title> 

<style type="text/css"> 

.normal{font-size:12px; background-color:#EEE; border-top:1px solid #DDD; border-bottom:1px solid #DDD; cursor:hand;} 

.mouseover{font-size:12px; background-color:#F4F4F4; border-top:1px solid #DDD; border-bottom:1px solid #DDD; cursor:hand;} 

</style> 

<script language=javascript> 

/*        注意事项                                */ 

/*    [、DIV对象注意加上事件onmousemove        */ 

/*    /、ID,NAME属性有一定的规律                */ 


document.onmousemove = MouseMove; 

document.onmouseout = MouseOut; 

document.onmouseup   = mouseUp; 

document.onload = InitArray; 


var mousedownLeft = 0; //鼠标按下时的位置 

var mousedownTop = 0; //鼠标按下时的位置 

var lastObj = null; //最后一次被覆盖的对象 

var lastLeft = 0; //最后一次被覆盖的对象位置 

var lastTop = 0; //最后一次被覆盖的对象位置 


var firstLineHeight = 169; //第一行的离上边框的距离 

var lineHeight = 28; //每一行的高度 

var layerName = "layer"; //层的名称 


var dragObject  = null; //被拖动的对象 

var mouseOffset = null; //鼠标离 被拖动对象 的左边或上边距离 

var arrLayer = new Array(); 


function InitArray(){ 

    var oRow = document.getElementsByTagName("DIV"); 

    var arrCount = 0; 

    //alert(oRow.length); 

    for(var i=0;i<oRow.length;i++){ 

        //alert(oRow[i].id.indexOf(layerName)); 

        if(oRow[i].id.indexOf(layerName) >= 0){ 

            //alert(oRow[i].id); 

            arrLayer[arrCount] = oRow[i]; 

            arrCount++; 

        } 

    } 

} 



function ResetRow(){ //重新排列行 

    var dragRow = event.srcElement; //当前拖动的行对象 

    var dragObjIndex=-1; //当前拖动对象在数组arrLayer中的索引值index 

    var nearObjIndex=-1; //最接近被覆盖对象在数组arrLayer中的索引值index 

    //alert(arrLayer.length); 

    for(var i=0;i<arrLayer.length;i++){ 

        //alert(arrLayer[i].id); 

        if(dragObjIndex==-1){ 

            if(arrLayer[i].id == dragRow.id){ 

                dragObjIndex = i; 

                i=0; 

            } 

        } 

        

        if((arrLayer[i].id!=dragRow.id) && dragObjIndex>-1){ 

            //alert(arrLayer[i].id + "," + dragRow.id); 

            //alert(i + "," + dragRow.style.pixelTop + "," + arrLayer[i].style.pixelTop); 

            if(Math.abs(dragRow.style.pixelTop - arrLayer[i].style.pixelTop) <= 15){ 

                

                //var leftPos = oRow[i].style.left; 

                //dragRow.style.top = arrLayer[i].style.pixelTop; 

                //dragRow.style.left = arrLayer[i].style.pixelLeft; 

                //document.all("textfield5").value += (arrLayer[i].id + ","); 

                var tmpLastObj = arrLayer[i]; 

                var tmpLastLeft = arrLayer[i].style.pixelLeft; 

                var tmpLastTop = arrLayer[i].style.pixelTop; 

                

                arrLayer[dragObjIndex] = arrLayer[i]; 

                arrLayer[i].style.top = lastTop; 

                arrLayer[i] = lastObj; 

                

                lastObj = tmpLastObj; 

                lastLeft = tmpLastLeft; 

                lastTop = tmpLastTop; 


                dragObject = null; 

                

                break; 

                //return; 

            } 

        } 

    } 

} 


function MouseMove(ev){ 

    ev = ev || window.event; 

    var mousePos = MouseCoord(ev); 

    var dragRow = event.srcElement; 

    document.all("mouseX").value = mousePos.x; 

    document.all("mouseY").value = mousePos.y; 

    

    if(dragObject){ 

        dragObject.style.postion = "absolute"; 

        dragObject.style.left = mousePos.x - mouseOffset.x; 

        dragObject.style.top = mousePos.y - mouseOffset.y; 

        ///*开始计算 

        if(dragRow.tagName=="DIV"){ 

            //alert(dragRow.style.top + "," + firstLineHeight); 

            document.all("textfield5").value = "===" + lastTop + "==="; 

            if((Math.abs(dragRow.style.pixelTop - lastTop) > 20) && (dragRow.style.pixelTop > firstLineHeight)){ //当竖向移动超过十五象素且移动的位置于第一行之下 

                //alert(); 

                

                ResetRow(); 

            }            

        } 

        

        //计算结束*/ 

        return false; 

    } 

    if(dragRow.tagName=="DIV"){dragRow.className="mouseover";    } 

    //alert(event.srcElement.tagName); 

    

} 

                  

function MouseOut(ev){ 

    ev = ev || window.event; 

    if(event.srcElement.tagName=="DIV"){ 

        event.srcElement.className="normal"; 

    } 

    dragObject = null; 

} 


function MouseCoord(ev){ 

    // 

    if(ev.pageX || ev.pageY){ 

          return {x:ev.pageX, y:ev.pageY}; 

     } 

    

    return { 

        x:ev.clientX + document.body.scrollLeft - document.body.clientLeft, 

        y:ev.clientY + document.body.scrollTop - document.body.clientTop 

    }; 

} 


//获取 鼠标离 被拖动对象 的左边或上边距离 

//target 

function getMouseOffset(target,ev){ 

    ev = ev || window.event; 

    var objPos = ObjPostion(target); 

    var mousePos = MouseCoord(ev); 

    return{ 

        x:mousePos.x - objPos.x, 

        y:mousePos.y - objPos.y 

    }; 

} 


//获取 对象Left、Top离浏览器的距离 

function ObjPostion(e){ 

    var left=0; 

    var top=0; 

    while(e.offsetParent){ 

        left += e.offsetLeft; 

        top += e.offsetTop; 

        e = e.offsetParent; 

    } 

    left += e.offsetLeft; 

    top += e.offsetTop; 

    return{x:left,y:top}; 

} 


function DragObj(item){ 

    if(!item){return;} 

    item.onmousedown = function(ev){ 

        dragObject = this; 

        mouseOffset = getMouseOffset(item); 

        mousedownLeft = event.srcElement.style.pixelLeft; 

        mousedownTop = event.srcElement.style.pixelTop; 

        lastLeft = event.srcElement.style.pixelLeft; 

        lastTop = event.srcElement.style.pixelTop; 

        lastObj = event.srcElement; 

        event.srcElement.style.zIndex=1000; 

        // alert(mousedownTop); 

        return; 

    } 

} 


function mouseUp(){ 

    var dragRow = event.srcElement; 

    dragObject = null; 

    dragRow.style.zIndex=5; 

    //alert(dragRow.style.pixelTop + "," + mousedownTop) 

    if(dragRow.tagName=="DIV"){ 

            //alert(dragRow.style.top + "," + firstLineHeight); 

            if((Math.abs(dragRow.style.pixelTop - lastTop) < 10) || (dragRow.style.pixelTop > firstLineHeight)){ //当竖向移动超过十五象素且移动的位置于第一行之下 

                //alert(lastObj.id + " , " + lastLeft + " , " + lastTop); 

                //dragRow.style.left = mousedownLeft; 

                //dragRow.style.top = mousedownTop; 

                dragRow.style.left = lastLeft; 

                //alert(lastTop); 

                dragRow.style.top = lastTop; 

                //ResetRow() 

                //调试信息 

                for(var k=0;k<arrLayer.length;k++){ 

                    var objText = document.getElementById("textfield" + (k+1)); 

                    var objLayer = document.getElementById("layer" + (k+1)); 

                    objText.value += objLayer.id; 

                    objText.value += ("," + objLayer.style.pixelLeft); 

                    objText.value += ("," + objLayer.style.pixelTop); 

                } 

                document.all("textfield4").value += lastTop; 

                document.all("textfield5").value += (lastObj.id + ","); 

            } 

            //else{ 

                //dragRow.style.left = mousedownLeft; 

                //dragRow.style.top = mousedownTop; 

            //} 

            

    } 

    //else{ 

                //dragRow.style.left = mousedownLeft; 

                //dragRow.style.top = mousedownTop; 

    //} 

    

} 

</script> 

<script language="JavaScript" type="text/JavaScript"> 

<!-- 

function MM_reloadPage(init) {  //reloads the window if Nav4 resized 

  if (init==true) with (navigator) {if ((appName=="Netscape")&&(parseInt(appVersion)==4)) { 

    document.MM_pgW=innerWidth; document.MM_pgH=innerHeight; οnresize=MM_reloadPage; }} 

  else if (innerWidth!=document.MM_pgW || innerHeight!=document.MM_pgH) location.reload(); 

} 

MM_reloadPage(true); 

//--> 

</script> 

</head> 


<body> 

<div id="Layer6" style="position:absolute; left:150px; top:47px; width:135px; height:52px; z-index:1; border:1px solid #EEE; background-color:#DDD;" οnmοusemοve="DragObj(this)">THIS IS A DRAG OBJECT.HI </div> 

<div id="layer1" name="Layer" style="position:absolute; left:14px; top:169px; width:606px; height:25px; z-index:5" class="normal" οnmοusemοve="DragObj(this)">the first </div> 

<div id="layer2" name="Layer" style="position:absolute; left:13px; top:199px; width:607px; height:25px; z-index:5" class="normal" οnmοusemοve="DragObj(this)">the second </div> 

<div id="layer3" name="Layer" style="position:absolute; left:13px; top:229px; width:607px; height:25px; z-index:5" class="normal" οnmοusemοve="DragObj(this)">the third </div> 

<p> </p> 

<p>MouseX 

  <input name="mouseX" type="text" id="mouseX" size="12"> 

</p> 

<p>MouseY 

  <input name="mouseY" type="text" id="mouseY" size="12"> 

</p> 

<p> 

  <input name="textfield1" type="text" size="18"> 

  <input name="textfield2" type="text" size="18"> 

  <input name="textfield3" type="text" size="18"> 

  <input name="textfield4" type="text" size="18"> 

  <input name="textfield5" type="text" id="textfield5" size="18"> 

  <script language=javascript> 

InitArray(); 

</script> 

</p> 

</body> 

</html>