譯者: Flyingis

    終于完成了全文的翻譯,由于時間比較參促,文章沒有過多的校正與潤色,閱讀過程中難免會有些許生硬或不準(zhǔn)確的感覺,請大家見量并指出,方便他人閱讀。

    原文作者將拖放功能的實(shí)現(xiàn)分步講解,其核心的地方在于移動和放置元素時,鼠標(biāo)、移動元素、目標(biāo)元素關(guān)系的處理,只要這個問題處理好了,代碼就很容易理解,譯文僅供輔助參考之用。

    整合所有的功能

    最后我們使用所有代碼片斷,來創(chuàng)建一個完整的拖放函數(shù)腳本。我們所要做的第一件事情是DOM操作,如果你對此并不十分熟悉,可以閱讀《
JavaScript Primer on DOM Manipulation 》。

    接下來的代碼創(chuàng)建容器和容器組,使得在這些容器中可以拖動每個元素,這在本文第二個demo的基礎(chǔ)上來完成。這段代碼能夠用來重新規(guī)劃元素的順序,將導(dǎo)航窗口放在頁面的左側(cè)或右側(cè),或再加入你所能想到的其他的功能。

    我們將使用偽代碼來一步步進(jìn)行講解,將真實(shí)的代碼通過注釋的方式留給讀者查看。

    1.當(dāng)文檔第一次被加載時,我們創(chuàng)建一個名為dragHelper的DIV標(biāo)簽,當(dāng)我們開始移動一個元素的時候,dragHelper將成為一個隱藏元素,可以四處移動。真實(shí)的元素并不會被拖動,僅僅使用insertBefore和appendChild來移動。我們在開始的時候隱藏dragHelper。

    2.我們創(chuàng)建mouseDown和mouseUp函數(shù)。起初,所有的這些函數(shù)都假設(shè)記錄了鼠標(biāo)按鈕的狀態(tài),以至于iMouseDown變量在鼠標(biāo)按下的時候?yàn)閠rue,沒有按下的時候?yàn)閒alse。

    3.我們創(chuàng)建一個全局變量DragDrops,以及一個函數(shù)CreateDragContainer。DragDrops包含一組相互關(guān)聯(lián)的容器。傳入CreateDragContainer的任何變量(代表容器)被組織成一個新的集合,使元素能夠在這些容器間自由移動。通過setAttribute,CreateDragContainer函數(shù)同樣將各容器中的元素綁定在一起。

    4.現(xiàn)在我們的代碼知道每個元素所在的集合,現(xiàn)在來看mouseMove函數(shù)。mouseMove函數(shù)首先設(shè)置了一個變量target,表示鼠標(biāo)下面的目標(biāo)元素,如果這個元素在集合(用getAttribute判斷)中就繼續(xù)下面操作:

    4.1.首先,在必要的時候,我們運(yùn)行一個簡單的腳本來改變目標(biāo)元素的class屬性,這樣就創(chuàng)造了一個翻動的效果。

    4.2.然后我們檢查鼠標(biāo)是否點(diǎn)擊(因?yàn)槲覀兊拇a已經(jīng)運(yùn)行到這里),如果事件發(fā)生:

    4.2.1.設(shè)置變量curTarget為當(dāng)前元素。
    4.2.2.記錄元素當(dāng)前在文檔中的位置,以便在需要的時候可以將它的值取回。
    4.2.3.將當(dāng)前元素克隆到dragHelper,使得我們能夠移動元素的隱藏備份。
    4.2.4.因?yàn)樵赿ragHelper中我們完全擁有了拖動元素的一個備份,這個元素會始終在鼠標(biāo)下,我們必須移除dragObj屬性,讓代碼知道dragObj已不在集合中。
    4.2.5.我們快速記錄集合中每個元素當(dāng)前的位置、寬度和高度。當(dāng)元素第一次開始被拖動時,我們僅需做一次這種工作,否則每當(dāng)鼠標(biāo)移動的時候我們都必須做一次,甚至一秒內(nèi)幾百次。

    4.3.如果鼠標(biāo)沒有點(diǎn)擊,要么我們和之前擁有同樣的目標(biāo)元素,要么沒有目標(biāo)元素,不論哪種情況我們都不會做任何事情。

    5.現(xiàn)在我們檢查curTarget變量。curTarget應(yīng)該僅包含一個被拖動的對象,因此如果它存在,表示我們正在拖動一個元素:

    5.1.移動隱藏DIV到鼠標(biāo),這個元素和文章前面所創(chuàng)建的元素一樣能夠被拖動。

    5.2.然后我們檢查鼠標(biāo)是否存在于當(dāng)前集合中每個容器中。

    5.2.1.如果鼠標(biāo)在某個容器中,我們檢查容器中的每個元素,查看我們正拖動的元素屬于哪個位置。
    5.2.2.然后我們將所拖動的元素放置在容器中另一個元素的前面,或容器的最后位置。
    5.2.3.最后我們確定元素可見。

    6.剩下的事情就是捕獲mouseUp事件:
    6.1.首先需要隱藏dragHelper:它不再被需要,因?yàn)槲覀儧]有拖動任何東西。
    6.2.如果拖動的元素是可見的,它已經(jīng)存在于任何它所屬的容器中,所有工作已完成。
    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 >

    關(guān)于作者

    Mark Kahn是一位Web Developer和DBA。可以通過這個網(wǎng)址跟他聯(lián)系:
http://www.jslibrary.org

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

    另外兩篇:
[翻譯] 如何在 JavaScript 中實(shí)現(xiàn)拖放(上)    [翻譯] 如何在 JavaScript 中實(shí)現(xiàn)拖放(中)