Flyingis

Talking and thinking freely !
Flying in the world of GIS !
随笔 - 156, 文章 - 16, 评论 - 589, 引用 - 0
数据加载中……

[翻译] 如何在 JavaScript 中实现拖放(下)

    译者: Flyingis

    终于完成了全文的翻译,由于时间比较参促,文章没有过多的校正与润色,阅读过程中难免会有些许生硬或不准确的感觉,请大家见量并指出,方便他人阅读。

    原文作者将拖放功能的实现分步讲解,其核心的地方在于移动和放置元素时,鼠标、移动元素、目标元素关系的处理,只要这个问题处理好了,代码就很容易理解,译文仅供辅助参考之用。

    整合所有的功能

    最后我们使用所有代码片断,来创建一个完整的拖放函数脚本。我们所要做的第一件事情是DOM操作,如果你对此并不十分熟悉,可以阅读《
JavaScript Primer on DOM Manipulation 》。

    接下来的代码创建容器和容器组,使得在这些容器中可以拖动每个元素,这在本文第二个demo的基础上来完成。这段代码能够用来重新规划元素的顺序,将导航窗口放在页面的左侧或右侧,或再加入你所能想到的其他的功能。

    我们将使用伪代码来一步步进行讲解,将真实的代码通过注释的方式留给读者查看。

    1.当文档第一次被加载时,我们创建一个名为dragHelper的DIV标签,当我们开始移动一个元素的时候,dragHelper将成为一个隐藏元素,可以四处移动。真实的元素并不会被拖动,仅仅使用insertBefore和appendChild来移动。我们在开始的时候隐藏dragHelper。

    2.我们创建mouseDown和mouseUp函数。起初,所有的这些函数都假设记录了鼠标按钮的状态,以至于iMouseDown变量在鼠标按下的时候为true,没有按下的时候为false。

    3.我们创建一个全局变量DragDrops,以及一个函数CreateDragContainer。DragDrops包含一组相互关联的容器。传入CreateDragContainer的任何变量(代表容器)被组织成一个新的集合,使元素能够在这些容器间自由移动。通过setAttribute,CreateDragContainer函数同样将各容器中的元素绑定在一起。

    4.现在我们的代码知道每个元素所在的集合,现在来看mouseMove函数。mouseMove函数首先设置了一个变量target,表示鼠标下面的目标元素,如果这个元素在集合(用getAttribute判断)中就继续下面操作:

    4.1.首先,在必要的时候,我们运行一个简单的脚本来改变目标元素的class属性,这样就创造了一个翻动的效果。

    4.2.然后我们检查鼠标是否点击(因为我们的代码已经运行到这里),如果事件发生:

    4.2.1.设置变量curTarget为当前元素。
    4.2.2.记录元素当前在文档中的位置,以便在需要的时候可以将它的值取回。
    4.2.3.将当前元素克隆到dragHelper,使得我们能够移动元素的隐藏备份。
    4.2.4.因为在dragHelper中我们完全拥有了拖动元素的一个备份,这个元素会始终在鼠标下,我们必须移除dragObj属性,让代码知道dragObj已不在集合中。
    4.2.5.我们快速记录集合中每个元素当前的位置、宽度和高度。当元素第一次开始被拖动时,我们仅需做一次这种工作,否则每当鼠标移动的时候我们都必须做一次,甚至一秒内几百次。

    4.3.如果鼠标没有点击,要么我们和之前拥有同样的目标元素,要么没有目标元素,不论哪种情况我们都不会做任何事情。

    5.现在我们检查curTarget变量。curTarget应该仅包含一个被拖动的对象,因此如果它存在,表示我们正在拖动一个元素:

    5.1.移动隐藏DIV到鼠标,这个元素和文章前面所创建的元素一样能够被拖动。

    5.2.然后我们检查鼠标是否存在于当前集合中每个容器中。

    5.2.1.如果鼠标在某个容器中,我们检查容器中的每个元素,查看我们正拖动的元素属于哪个位置。
    5.2.2.然后我们将所拖动的元素放置在容器中另一个元素的前面,或容器的最后位置。
    5.2.3.最后我们确定元素可见。

    6.剩下的事情就是捕获mouseUp事件:
    6.1.首先需要隐藏dragHelper:它不再被需要,因为我们没有拖动任何东西。
    6.2.如果拖动的元素是可见的,它已经存在于任何它所属的容器中,所有工作已完成。
    6.3.如果拖动的元素不可见,我们将它放回它原来所在的地方。
//  iMouseDown represents the current mouse button state: up or down
/**/ /*
lMouseState represents the previous mouse button state so that we can
check for button clicks and button releases:

if(iMouseDown && !lMouseState) // button just clicked!
if(!iMouseDown && lMouseState) // button just released!
*/

var  mouseOffset  =   null ;
var  iMouseDown   =   false
;
var  lMouseState  =   false
;
var  dragObject   =   null
;

//  Demo 0 variables

var  DragDrops    =  [];
var  curTarget    =   null
;
var  lastTarget   =   null
;
var  dragHelper   =   null
;
var  tempDiv      =   null
;
var  rootParent   =   null
;
var  rootSibling  =   null
;

Number.prototype.NaN0
= function () { return  isNaN( this ) ? 0 : this ;}


function  CreateDragContainer() {
  
/**/
/*
  Create a new "Container Instance" so that items from one "Set" can not
  be dragged into items from another "Set"
  
*/

  
var  cDrag  =  DragDrops.length;
  DragDrops[cDrag] 
=
 [];

  
/**/
/*
  Each item passed to this function should be a "container".  Store each
  of these items in our current container
  
*/

  
for ( var  i = 0 ; i < arguments.length; i ++ ) {
    
var  cObj  =
 arguments[i];
    DragDrops[cDrag].push(cObj);
    cObj.setAttribute('DropObj', cDrag);

    
/**/
/*
    Every top level item in these containers should be draggable.  Do this
    by setting the DragObj attribute on each item and then later checking
    this attribute in the mouseMove function
    
*/

    
for ( var  j = 0 ; j < cObj.childNodes.length; j ++ ) {

      
//  Firefox puts in lots of #text nodesskip these

       if (cObj.childNodes[j].nodeName == '#text')  continue ;

      cObj.childNodes[j].setAttribute('DragObj', cDrag);
    }

  }

}


function  mouseMove(ev) {
    ev 
=  ev  ||
 window.event;

    
/**/
/*
    We are setting target to whatever item the mouse is currently on
    Firefox uses event.target here, MSIE uses event.srcElement
    
*/

    
var  target  =  ev.target  ||  ev.srcElement;
    
var  mousePos  =
 mouseCoords(ev);

    
//  mouseOut event - fires if the item the mouse is on has changed

     if (lastTarget  &&  (target !== lastTarget)) {
      
//  reset the classname for the target element

       var  origClass  =  lastTarget.getAttribute('origClass');
      
if (origClass) lastTarget.className  =
 origClass;
    }


    
/**/ /*
    dragObj is the grouping our item is in (set from the createDragContainer function).
    if the item is not in a grouping we ignore it since it can't be dragged with this
    script.
    
*/

    
var  dragObj  =  target.getAttribute('DragObj');

    
//  if the mouse was moved over an element that is draggable

     if (dragObj != null ) {
      
//  mouseOver event - Change the item's class if necessary

       if (target != lastTarget) {
        
var  oClass  =
 target.getAttribute('overClass');
        
if (oClass)
{
          target.setAttribute('origClass', target.className);
          target.className 
=
 oClass;
        }

      }


      
//  if the user is just starting to drag the element
       if (iMouseDown  &&   ! lMouseState) {
        
//  mouseDown target

        curTarget  =  target;

        
//  Record the mouse x and y offset for the element

        rootParent  =  curTarget.parentNode;
        rootSibling 
=
 curTarget.nextSibling;

        mouseOffset   
=
 getMouseOffset(target, ev);

        
//  We remove anything that is in our dragHelper DIV so we can put a new item in it.

         for ( var  i = 0 ; i < dragHelper.childNodes.length; i ++ ) dragHelper.removeChild(dragHelper.childNodes[i]);

        
//  Make a copy of the current item and put it in our drag helper.

        dragHelper.appendChild(curTarget.cloneNode( true ));
        dragHelper.style.display 
=
 'block';

        
//  set the class on our helper DIV if necessary

         var  dragClass  =  curTarget.getAttribute('dragClass');
        
if (dragClass)
{
          dragHelper.firstChild.className 
=
 dragClass;
        }


        
//  disable dragging from our helper DIV (it's already being dragged)
        dragHelper.firstChild.removeAttribute('DragObj');

        
/**/
/*
        Record the current position of all drag/drop targets related
        to the element.  We do this here so that we do not have to do
        it on the general mouse move event which fires when the mouse
        moves even 1 pixel.  If we don't do this here the script
        would run much slower.
        
*/

        
var  dragConts  =  DragDrops[dragObj];

        
/**/
/*
        first record the width/height of our drag item.  Then hide it since
        it is going to (potentially) be moved out of its parent.
        
*/

        curTarget.setAttribute('startWidth',  parseInt(curTarget.offsetWidth));
        curTarget.setAttribute('startHeight', parseInt(curTarget.offsetHeight));
        curTarget.style.display  
=  'none';

        
//  loop through each possible drop container

         for ( var  i = 0 ; i < dragConts.length; i ++ ) {
          
with (dragConts[i])
{
            
var  pos  =
 getPosition(dragConts[i]);

            
/**/
/*
            save the width, height and position of each container.

            Even though we are saving the width and height of each
            container back to the container this is much faster because
            we are saving the number and do not have to run through
            any calculations again.  Also, offsetHeight and offsetWidth
            are both fairly slow.  You would never normally notice any
            performance hit from these two functions but our code is
            going to be running hundreds of times each second so every
            little bit helps!

            Note that the biggest performance gain here, by far, comes
            from not having to run through the getPosition function
            hundreds of times.
            
*/

            setAttribute('startWidth', parseInt(offsetWidth));
            setAttribute('startHeight', parseInt(offsetHeight));
            setAttribute('startLeft', pos.x);
            setAttribute('startTop', pos.y);
          }


          
//  loop through each child element of each container
           for ( var  j = 0 ; j < dragConts[i].childNodes.length; j ++ ) {
            
with (dragConts[i].childNodes[j])
{
              
if ((nodeName == '#text')  ||  (dragConts[i].childNodes[j] == curTarget))  continue
;

              
var  pos  =
 getPosition(dragConts[i].childNodes[j]);

              
//  save the width, height and position of each element

              setAttribute('startWidth',  parseInt(offsetWidth));
              setAttribute('startHeight', parseInt(offsetHeight));
              setAttribute('startLeft',   pos.x);
              setAttribute('startTop',    pos.y);
            }

          }

        }

      }

    }


    
//  If we get in here we are dragging something
     if (curTarget) {
      
//  move our helper div to wherever the mouse is (adjusted by mouseOffset)

      dragHelper.style.top   =  mousePos.y  -  mouseOffset.y;
      dragHelper.style.left 
=  mousePos.x  -
 mouseOffset.x;

      
var  dragConts   =
 DragDrops[curTarget.getAttribute('DragObj')];
      
var  activeCont  =   null
;

      
var  xPos  =  mousePos.x  -  mouseOffset.x  +  (parseInt(curTarget.getAttribute('startWidth'))  / 2
);
      
var  yPos  =  mousePos.y  -  mouseOffset.y  +  (parseInt(curTarget.getAttribute('startHeight')) / 2
);

      
//  check each drop container to see if our target object is "inside" the container

       for ( var  i = 0 ; i < dragConts.length; i ++ ) {
        
with (dragConts[i])
{
          
if (((getAttribute('startLeft'))  <  xPos)  &&

            ((getAttribute('startTop')) 
<  yPos)  &&
            ((getAttribute('startLeft') 
+  getAttribute('startWidth'))  >  xPos)  &&
            ((getAttribute('startTop')  
+  getAttribute('startHeight'))  >  yPos)) {

            
/**/
/*
            our target is inside of our container so save the container into
            the activeCont variable and then exit the loop since we no longer
            need to check the rest of the containers
            
*/

            activeCont 
=  dragConts[i];

            
//  exit the for loop

             break ;
        }

      }

    }


    
//  Our target object is in one of our containers.  Check to see where our div belongs
     if (activeCont) {
      
//  beforeNode will hold the first node AFTER where our div belongs

       var  beforeNode  =   null ;

      
//  loop through each child node (skipping text nodes).

       for ( var  i = activeCont.childNodes.length - 1 ; i >= 0 ; i -- ) {
        
with (activeCont.childNodes[i])
{
          
if (nodeName == '#text')  continue
;

            
//  if the current item is "After" the item being dragged

             if (
              curTarget 
!=  activeCont.childNodes[i]  &&

              ((getAttribute('startLeft') 
+  getAttribute('startWidth'))  >  xPos)  &&
              ((getAttribute('startTop')  
+  getAttribute('startHeight'))  >  yPos)) {
                beforeNode 
=
 activeCont.childNodes[i];
        }

      }

    }


    
//  the item being dragged belongs before another item
     if (beforeNode) {
      
if (beforeNode != curTarget.nextSibling)
{
        activeCont.insertBefore(curTarget, beforeNode);
      }


    
//  the item being dragged belongs at the end of the current container
    }
  else   {
      
if ((curTarget.nextSibling)  ||  (curTarget.parentNode != activeCont))
{
        activeCont.appendChild(curTarget);
      }

    }


    
//  make our drag item visible
     if (curTarget.style.display != '') {
      curTarget.style.display  
=
 '';
    }

  }
  else   {

    
//  our drag item is not in a container, so hide it.

     if (curTarget.style.display != 'none') {
      curTarget.style.display  
=
 'none';
    }

  }

}


  
//  track the current mouse state so we can compare against it next time
  lMouseState  =  iMouseDown;

  
//  mouseMove target

  lastTarget   =  target;

  
//  track the current mouse state so we can compare against it next time

  lMouseState  =  iMouseDown;

  
//  this helps prevent items on the page from being highlighted while dragging

   return   false ;
}


function  mouseUp(ev) {
  
if (curTarget)
{
    
//  hide our helper object - it is no longer needed

    dragHelper.style.display  =  'none';

    
//  if the drag item is invisible put it back where it was before moving it

     if (curTarget.style.display  ==  'none') {
      
if (rootSibling)
{
        rootParent.insertBefore(curTarget, rootSibling);
      }
  else  
{
        rootParent.appendChild(curTarget);
      }

    }


    
//  make sure the drag item is visible
    curTarget.style.display  =  '';
  }

  curTarget  
=   null ;
  iMouseDown 
=   false
;
}


function  mouseDown() {
  iMouseDown 
=   true
;
  
if (lastTarget)
{
    
return   false
;
  }

}


document.onmousemove 
=  mouseMove;
document.onmousedown 
=
 mouseDown;
document.onmouseup 
=
 mouseUp;

window.onload 
=   function ()
{
  
//  Create our helper object that will show the item while dragging

  dragHelper  =  document.createElement('DIV');
  dragHelper.style.cssText 
=
 'position:absolute;display:none;';
        
  CreateDragContainer(
    document.getElementById('DragContainer1'),
    document.getElementById('DragContainer2'),
    document.getElementById('DragContainer3')
  );

  document.body.appendChild(dragHelper);
}
<!--  the mouse over and dragging class are defined on each item  -->
 
< div  class  ="DragContainer"  id ="DragContainer1" >
  
< div  class ="DragBox"  id ="Item1"  overClass ="OverDragBox"  dragClass ="DragDragBox" > Item #1 </ div >
  
< div  class ="DragBox"  id ="Item2"  overClass ="OverDragBox"  dragClass ="DragDragBox" > Item #2 </ div >
  
< div  class ="DragBox"  id ="Item3"  overClass ="OverDragBox"  dragClass ="DragDragBox" > Item #3 </ div >
  
< div  class ="DragBox"  id ="Item4"  overClass ="OverDragBox"  dragClass ="DragDragBox" > Item #4 </ div >  
</ div >


< div  class ="DragContainer"  id ="DragContainer2"   >
  
< div  class ="DragBox"  id ="Item5"  overClass ="OverDragBox"  dragClass ="DragDragBox" > Item #5 </ div >
  
< div  class ="DragBox"  id ="Item6"  overClass ="OverDragBox"  dragClass ="DragDragBox" > Item #6 </ div >
  
< div  class ="DragBox"  id ="Item7"  overClass ="OverDragBox"  dragClass ="DragDragBox" > Item #7 </ div >
  
< div  class ="DragBox"  id ="Item8"  overClass ="OverDragBox"  dragClass ="DragDragBox" > Item #8 </ div >
</ div >

< div  class ="DragContainer"  id ="DragContainer3" >
  
< div  class ="DragBox"  id ="Item9"  overClass ="OverDragBox"  dragClass ="DragDragBox" > Item #9 </ div >
  
< div  class ="DragBox"  id ="Item10"  overClass ="OverDragBox"  dragClass ="DragDragBox" > Item #10 </ div >
  
< div  class ="DragBox"  id ="Item11"  overClass ="OverDragBox"  dragClass ="DragDragBox" > Item #11 </ div >
  
< div  class ="DragBox"  id ="Item12"  overClass ="OverDragBox"  dragClass ="DragDragBox" > Item #12 </ div >
</ div >

    关于作者

    Mark Kahn是一位Web Developer和DBA。可以通过这个网址跟他联系:
http://www.jslibrary.org

    原文链接: http://www.webreference.com/programming/javascript/mk/column2/3.html 

    另外两篇:
[翻译] 如何在 JavaScript 中实现拖放(上)    [翻译] 如何在 JavaScript 中实现拖放(中)

posted on 2006-10-15 17:03 Flyingis 阅读(6642) 评论(19)  编辑  收藏 所属分类: Web 客户端技术

评论

# re: [翻译] 如何在 JavaScript 中实现拖放(下)  回复  更多评论   

期待这篇文章很久了,呵呵,辛苦啦
2006-10-15 17:44 | 马嘉楠

# re: [翻译] 如何在 JavaScript 中实现拖放(下)  回复  更多评论   

@马嘉楠
时间紧,翻译的还有一些问题,欢迎意见!
2006-10-15 18:11 | Flyingis

# re: [翻译] 如何在 JavaScript 中实现拖放(下)  回复  更多评论   

好东西,能不能给出一个完整的测试用例!谢谢啦!
2006-10-15 20:43 | jerson20081024

# re: [翻译] 如何在 JavaScript 中实现拖放(下)  回复  更多评论   

@jerson20081024
有空我整理出来。
2006-10-16 08:55 | Flyingis

# re: [翻译] 如何在 JavaScript 中实现拖放(下)  回复  更多评论   

我试了一下,用简单的可以成功,可是不知道为什么,如:
< div class ="DragBox" id ="Item12" overClass ="OverDragBox" dragClass ="DragDragBox" > Item #12 </ div >
中的Item #12 换成表格或是标签层类的内容,拖动功能有时就出差错了,拖动时,在IE中整个列会消失,不知道为什么,您可以解释一下么?
谢谢
2006-10-16 11:53 | xiaoyuer

# re: [翻译] 如何在 JavaScript 中实现拖放(下)  回复  更多评论   

我把JS代码整理出来了,可以下载:
http://www.blogjava.net/Files/flyingis/drag_drop.rar
@xiaoyuer
我没有仔细调试,但代码中多处涉及父子节点的操作,换成标签层后DOM深度改变,可能因此而影响代码逻辑功能。
2006-10-16 22:01 | Flyingis

# re: [翻译] 如何在 JavaScript 中实现拖放(下)  回复  更多评论   

@Flyingis
谢谢!!
2006-10-17 10:44 | xiaoyuer

# re: [翻译] 如何在 JavaScript 中实现拖放(下)  回复  更多评论   

谢谢!ke!
2006-10-17 16:06 | ghdvb

# re: [翻译] 如何在 JavaScript 中实现拖放(下)  回复  更多评论   

感谢翻译!期待例子。
2006-10-17 19:56 | 慈悲鱼

# re: [翻译] 如何在 JavaScript 中实现拖放(下)  回复  更多评论   

@Flyingis
今天偶然打开那个作的测试页面,发现问题了,呵呵,那个问题好像是和IE浏览器有关,我一直用Maxthon 今天无意用IE打开就没出现意外情况,继续研究!
2006-10-18 10:03 | xiaoyuer

# 能否保存经过拖放后的效果?  回复  更多评论   

能否保存经过拖放后的效果?
2006-12-07 19:30 | tom[匿名]

# re: [翻译] 如何在 JavaScript 中实现拖放(下)  回复  更多评论   

http://www.cnblogs.com/cloudgamer/archive/2008/11/17/1334778.html
这是我写的拖动效果
多多交流
2008-11-23 17:15 | vvd

# re: [翻译] 如何在 JavaScript 中实现拖放(下)  回复  更多评论   

请问:mouseUp函数中最后如何获取rootParent对象中的数据?
2009-03-25 16:33 | 小小陈

# android tablets  回复  更多评论   

的拖动效果
2013-02-09 21:37 | android tablets

# free movies online  回复  更多评论   

今天偶然打开那个作的测试页面,发现问题了,呵呵,那个问题好像是和IE浏览器有关,我一直用Maxthon 今天无意用IE打开就没出现意外情况,继续研究!
2013-03-11 17:21 | free movies online

# re: [翻译] 如何在 JavaScript 中实现拖放(下)  回复  更多评论   

这是我写的拖动效果
多多交流
2013-03-12 16:22 | movies on demand

# movies on line  回复  更多评论   

피쉬 케이크! 이것이 내가 노력 죽어 한 한국어 진미입니다.
2013-03-13 17:46 | movies on line

# movies online  回复  更多评论   

请问:mouseUp函数中最后如何获取rootParent对象中的数据?
2013-03-14 16:26 | movies online

# re: [翻译] 如何在 JavaScript 中实现拖放(下)[未登录]  回复  更多评论   

为什么我的那个drapHelper一直提示是null
2013-06-04 10:40 | zoe

只有注册用户登录后才能发表评论。


网站导航: