對google個性主頁的拖拽效果的js的完整註釋

這兩天太忙了,沒時間寫Blog,業餘時間又扒了一次google的拖拽,對比了netvibes的,差不多,讓後就寫了個註釋,順便學習。
google用了混淆,js裏面的命名比較暈。我花了些時間把他們翻譯成容易看懂的命名,並且作了註釋,希望對大家有幫助。
可以這裏下載我的例子:
http://www.blogjava.net/Files/iamtin/google_drag.rar

// 工具類,使用Util的命名空間,方便管理
var  Util  =   new  Object();
// 獲取http header裏面的UserAgent,瀏覽器信息
Util.getUserAgent  =  navigator.userAgent;
// 是否是Gecko核心的Browser,比如Mozila、Firefox
Util.isGecko  =  Util.getUserAgent.indexOf( " Gecko " !=   - 1 ;
// 是否是Opera
Util.isOpera  =  Util.getUserAgent.indexOf( " Opera " !=   - 1 ;
// 獲取一個element的offset信息,其實就是相對於Body的padding以內的絕對座標
//
後面一個參數如果是true則獲取offsetLeft,false則是offsetTop
//
關於offset、style、client等座標的定義參考dindin的這個帖子:http://www.jroller.com/page/dindin/?anchor=pro_javascript_12
Util.getOffset  =   function  (el, isLeft) {
    
var  retValue  =   0 ;
    
while  (el  !=   null ) {
        retValue 
+=  el[ " offset "   +  (isLeft  ?   " Left "  :  " Top " )];
        el 
=  el.offsetParent;
    }
    
return  retValue;
};
// 將一個function(參數中的funcName是這個fuction的名字)綁定到一個element上,並且以這個element的上下文運行,其實是一種繼承,這個可以google些文章看看
Util.bindFunction  =   function  (el, fucName) {
    
return   function  () {
        
return  el[fucName].apply(el, arguments);
    };
};
// 重新計算所有的可以拖拽的element的座標,對同一個column下面的可拖拽圖層重新計算它們的高度而得出新的座標,防止遮疊
//
計算出來的座標記錄在pagePosLeft和pagePosTop兩個屬性裏面
Util.re_calcOff  =   function  (el) {
    
for  ( var  i  =   0 ; i  <  Util.dragArray.length; i ++ ) {
        
var  ele  =  Util.dragArray[i];
        ele.elm.pagePosLeft 
=  Util.getOffset(ele.elm,  true );
        ele.elm.pagePosTop 
=  Util.getOffset(ele.elm,  false );
    }
    
var  nextSib  =  el.elm.nextSibling;
    
while  (nextSib) {
        nextSib.pagePosTop 
-=  el.elm.offsetHeight;
        nextSib 
=  nextSib.nextSibling;
    }
};

// 隱藏Google Ig中間那個table,也就是拖拽的容器,配合show一般就是刷新用,解決一些瀏覽器的怪癖
Util.hide  =   function  () {
    Util.rootElement.style.display 
=   " none " ;
};
// 顯示Google Ig中間那個table,解釋同上
Util.show  =   function  () {
    Util.rootElement.style.display 
=   "" ;
};

// 移動時顯示的佔位虛線框
ghostElement  =   null ;
// 獲取這個虛線框,通過dom動態生成
getGhostElement  =   function  () {
    
if  ( ! ghostElement) {
        ghostElement 
=  document.createElement( " DIV " );
        ghostElement.className 
=   " modbox " ;
        ghostElement.backgroundColor 
=   "" ;
        ghostElement.style.border 
=   " 2px dashed #aaa " ;
        ghostElement.innerHTML 
=   " &nbsp; " ;
    }
    
return  ghostElement;
};

// 初始化可以拖拽的Element的函數,與拖拽無關的我去掉了
function  draggable(el) {
    
// 公用的開始拖拽的函數
     this ._dragStart  =  start_Drag;
    
// 公用的正在拖拽的函數
     this ._drag  =  when_Drag;
    
// 公用的拖拽結束的函數
     this ._dragEnd  =  end_Drag;
    
// 這個函數主要用來進行拖拽結束後的dom處理
     this ._afterDrag  =  after_Drag;
    
// 是否正在被拖動,一開始當然沒有被拖動
     this .isDragging  =   false ;
    
// 將這個Element的this指針註冊在elm這個變量裏面,方便在自己的上下文以外調用自己的函數等,很常用的方法
     this .elm  =  el;
    
// 觸發拖拽的Element,在這裏就是這個div上顯示標題的那個div
     this .header  =  document.getElementById(el.id  +   " _h " );
    
// 對於有iframe的element拖拽不同,這裏檢測一下並記錄
     this .hasIFrame  =   this .elm.getElementsByTagName( " IFRAME " ).length  >   0 ;
    
// 如果找到了header就綁定drag相關的event
     if  ( this .header) {
        
// 拖拽時的叉子鼠標指針
         this .header.style.cursor  =   " move " ;
        
// 將函數綁定到header和element的this上,參照那個函數的說明
        Drag.init( this .header,  this .elm);
        
// 下面三個語句將寫好的三個函數綁定給這個elemnt的三個函數鉤子上,也就實現了element從draggable繼承可拖拽的函數
         this .elm.onDragStart  =  Util.bindFunction( this " _dragStart " );
        
this .elm.onDrag  =  Util.bindFunction( this " _drag " );
        
this .elm.onDragEnd  =  Util.bindFunction( this " _dragEnd " );
    }
};

// 下面就是draggable裏面用到的那4個function
//
公用的開始拖拽的函數
function  start_Drag() {
    
// 重置座標,實現拖拽以後自己的位置馬上會被填充的效果
    Util.re_calcOff( this );
    
// 記錄原先的鄰居節點,用來對比是否被移動到新的位置
     this .origNextSibling  =   this .elm.nextSibling;
    
// 獲取移動的時候那個灰色的虛線框
     var  _ghostElement  =  getGhostElement();
    
// 獲取正在移動的這個對象的高度
     var  offH  =   this .elm.offsetHeight;
    
if  (Util.isGecko) {
        
// 修正gecko引擎的怪癖吧
        offH  -=  parseInt(_ghostElement.style.borderTopWidth)  *   2 ;
    }
    
// 獲取正在移動的這個對象的寬度
     var  offW  =   this .elm.offsetWidth;
    
// 獲取left和top的座標
     var  offLeft  =  Util.getOffset( this .elm,  true );
    
var  offTop  =  Util.getOffset( this .elm,  false );
    
// 防止閃爍,現隱藏
    Util.hide();
    
// 將自己的寬度記錄在style屬性裏面
     this .elm.style.width  =  offW  +   " px " ;
    
// 將那個灰框設定得與正在拖動的對象一樣高,比較形象
    _ghostElement.style.height  =  offH  +   " px " ;
    
// 把灰框放到這個對象原先的位置上
     this .elm.parentNode.insertBefore(_ghostElement,  this .elm.nextSibling);
    
// 由於要拖動必須將被拖動的對象從原先的盒子模型裏面抽出來,所以設定position爲absolute,這個可以參考一下css佈局方面的知識
     this .elm.style.position  =   " absolute " ;
    
// 設置zIndex,讓它處在最前面一層,當然其實zIndex=100是讓它很靠前,如果頁面裏有zIndex>100的,那……
     this .elm.style.zIndex  =   100 ;
    
// 由於position=absolute了,所以left和top實現絕對座標定位,這就是先前計算座標的作用,不讓這個模型亂跑,要從開始拖動的地方開始移動
     this .elm.style.left  =  offLeft  +   " px " ;
    
this .elm.style.top  =  offTop  +   " px " ;
    
// 座標設定完畢,可以顯示了,這樣就不會閃爍了
    Util.show();
    
// 這裏本來有個ig_d.G,沒搞明白幹什麼用的,不過沒有也可以用,誰知道麻煩告訴我一聲,不好意思
     // 還沒有開始拖拽,這裏做個記號
     this .isDragging  =   false ;
    
return   false ;
};
// 在拖拽時的相應函數,由於綁定到鼠標的move這個event上,所以會傳入鼠標的座標clientX, clientY
function  when_Drag(clientX, clientY) {
    
// 剛開始拖拽的時候將圖層變透明,並標記爲正在被拖拽
     if  ( ! this .isDragging) {
        
this .elm.style.filter  =   " alpha(opacity=70) " ;
        
this .elm.style.opacity  =   0.7 ;
        
this .isDragging  =   true ;
    }
    
// 被拖拽到的新的column(當然也可以是原來那個)
     var  found  =   null ;
    
// 最大的距離,可能是防止溢出或者什麼bug
     var  max_distance  =   100000000 ;
    
// 遍歷所有的可拖拽的element,尋找離當前鼠標座標最近的那個可拖拽元素,以便後面插入
     for  ( var  i  =   0 ; i  <  Util.dragArray.length; i ++ ) {
        
var  ele  =  Util.dragArray[i];
        
// 利用勾股定理計算鼠標到遍歷到的這個元素的距離
         var  distance  =  Math.sqrt(Math.pow(clientX  -  ele.elm.pagePosLeft,  2 +  Math.pow(clientY  -  ele.elm.pagePosTop,  2 ));
        
// 自己已經浮動了,所以不計算自己的
         if  (ele  ==   this ) {
            
continue ;
        }
        
// 如果計算失敗繼續循環
         if  (isNaN(distance)) {
            
continue ;
        }
        
// 如果更小,記錄下這個距離,並將它作爲found
         if  (distance  <  max_distance) {
            max_distance 
=  distance;
            found 
=  ele;
        }
    }
    
// 準備讓灰框落腳
     var  _ghostElement  =  getGhostElement();
    
// 如果找到了另外的落腳點
     if  (found  !=   null   &&  _ghostElement.nextSibling  !=  found.elm) {
        
// 找到落腳點就先把灰框插進去,這就是我們看到的那個灰框停靠的特效,有點像吸附的感覺,哈哈
        found.elm.parentNode.insertBefore(_ghostElement, found.elm);
        
if  (Util.isOpera) {
            
// Opera的現實問題,要隱藏/顯示後才能刷新出變化
            document.body.style.display  =   " none " ;
            document.body.style.display 
=   "" ;
        }
    }
};
// 拖拽完畢
function  end_Drag() {
    
// 拖拽完畢後執行後面的鉤子,執行after_Drag(),如果佈局發生了變動了就記錄到遠程服務器,保存你拖拽後新的佈局順序
     if  ( this ._afterDrag()) {
        
// remote call to save the change
    }
    
return   true ;
};
// 拖拽後的執行鉤子
function  after_Drag() {
    
var  returnValue  =   false ;
    
// 防止閃爍
    Util.hide();
    
// 把拖拽時的position=absolute和相關的那些style都消除
     this .elm.style.position  =   "" ;
    
this .elm.style.width  =   "" ;
    
this .elm.style.zIndex  =   "" ;
    
this .elm.style.filter  =   "" ;
    
this .elm.style.opacity  =   "" ;
    
// 獲取灰框
     var  ele  =  getGhostElement();
    
// 如果現在的鄰居不是原來的鄰居了
     if  (ele.nextSibling  !=   this .origNextSibling) {
        
// 把被拖拽的這個節點插到灰框的前面
        ele.parentNode.insertBefore( this .elm, ele.nextSibling);
        
// 標明被拖拽了新的地方
        returnValue  =   true ;
    }
    
// 移除灰框,這是這個灰框的生命週期應該就結束了
    ele.parentNode.removeChild(ele);
    
// 修改完畢,顯示
    Util.show();
    
if  (Util.isOpera) {
        
// Opera的現實問題,要隱藏/顯示後才能刷新出變化
        document.body.style.display  =   " none " ;
        document.body.style.display 
=   "" ;
    }
    
return  returnValue;
};
// 可拖拽Element的原形,用來將event綁定到各個鉤子,這部分市比較通用的,netvibes也是基本完全相同的實現
//
這部分推薦看dindin的這個,也會幫助理解,http://www.jroller.com/page/dindin/?anchor=pro_javascript_12
var  Drag  =  {
    
// 對這個element的引用,一次只能拖拽一個Element
    obj: null
    
// element是被拖拽的對象的引用,elementHeader就是鼠標可以拖拽的區域
    init: function  (elementHeader, element) {
        
// 將start綁定到onmousedown事件,按下鼠標觸發start
        elementHeader.onmousedown  =  Drag.start;
        
// 將element存到header的obj裏面,方便header拖拽的時候引用
        elementHeader.obj  =  element;
        
// 初始化絕對的座標,因爲不是position=absolute所以不會起什麼作用,但是防止後面onDrag的時候parse出錯了
         if  (isNaN(parseInt(element.style.left))) {
            element.style.left 
=   " 0px " ;
        }
        
if  (isNaN(parseInt(element.style.top))) {
            element.style.top 
=   " 0px " ;
        }
        
// 掛上空Function,初始化這幾個成員,在Drag.init被調用後才幫定到實際的函數,可以參照draggable裏面的內容
        element.onDragStart  =   new  Function();
        element.onDragEnd 
=   new  Function();
        element.onDrag 
=   new  Function();
    },
    
// 開始拖拽的綁定,綁定到鼠標的移動的event上
    start: function  (event) {
        
var  element  =  Drag.obj  =   this .obj;
        
// 解決不同瀏覽器的event模型不同的問題
        event  =  Drag.fixE(event);
        
// 看看是不是左鍵點擊
         if  (event.which  !=   1 ) {
            
// 除了左鍵都不起作用
             return   true ;
        }
        
// 參照這個函數的解釋,掛上開始拖拽的鉤子
        element.onDragStart();
        
// 記錄鼠標座標
        element.lastMouseX  =  event.clientX;
        element.lastMouseY 
=  event.clientY;
        
// 將Global的event綁定到被拖動的element上面來
        document.onmouseup  =  Drag.end;
        document.onmousemove 
=  Drag.drag;
        
return   false ;
    }, 
    
// Element正在被拖動的函數
    drag: function  (event) {
        
// 解決不同瀏覽器的event模型不同的問題
        event  =  Drag.fixE(event);
        
// 看看是不是左鍵點擊
         if  (event.which  ==   0 ) {
            
// 除了左鍵都不起作用
             return  Drag.end();
        }
        
// 正在被拖動的Element
         var  element  =  Drag.obj;
        
// 鼠標座標
         var  _clientX  =  event.clientY;
        
var  _clientY  =  event.clientX;
        
// 如果鼠標沒動就什麼都不作
         if  (element.lastMouseX  ==  _clientY  &&  element.lastMouseY  ==  _clientX) {
            
return   false ;
        }
        
// 剛纔Element的座標
         var  _lastX  =  parseInt(element.style.top);
        
var  _lastY  =  parseInt(element.style.left);
        
// 新的座標
         var  newX, newY;
        
// 計算新的座標:原先的座標+鼠標移動的值差
        newX  =  _lastY  +  _clientY  -  element.lastMouseX;
        newY 
=  _lastX  +  _clientX  -  element.lastMouseY;
        
// 修改element的顯示座標
        element.style.left  =  newX  +   " px " ;
        element.style.top 
=  newY  +   " px " ;
        
// 記錄element現在的座標供下一次移動使用
        element.lastMouseX  =  _clientY;
        element.lastMouseY 
=  _clientX;
        
// 參照這個函數的解釋,掛接上Drag時的鉤子
        element.onDrag(newX, newY);
        
return   false ;
    },
    
// Element正在被釋放的函數,停止拖拽
    end: function  (event) {
        
// 解決不同瀏覽器的event模型不同的問題
        event  =  Drag.fixE(event);
        
// 解除對Global的event的綁定
        document.onmousemove  =   null ;
        document.onmouseup 
=   null ;
        
// 先記錄下onDragEnd的鉤子,好移除obj
         var  _onDragEndFuc  =  Drag.obj.onDragEnd();
        
// 拖拽完畢,obj清空
        Drag.obj  =   null ;
        
return  _onDragEndFuc;
    }, 
    
// 解決不同瀏覽器的event模型不同的問題
    fixE: function  (ig_) {
        
if  ( typeof  ig_  ==   " undefined " ) {
            ig_ 
=  window.event;
        }
        
if  ( typeof  ig_.layerX  ==   " undefined " ) {
            ig_.layerX 
=  ig_.offsetX;
        }
        
if  ( typeof  ig_.layerY  ==   " undefined " ) {
            ig_.layerY 
=  ig_.offsetY;
        }
        
if  ( typeof  ig_.which  ==   " undefined " ) {
            ig_.which 
=  ig_.button;
        }
        
return  ig_;
    }
};



// 下面是初始化的函數了,看看上面這些東西怎麼被調用
var  _IG_initDrag  =   function  (el) {
    
// column那個容器,在google裏面就是那個table佈局的tbody,netvibes用的<div>
    Util.rootElement  =  el;
    
// 這個tbody的行
    Util._rows  =  Util.rootElement.tBodies[ 0 ].rows[ 0 ];
    
// 列,google是3列,其實也可以更多
    Util.column  =  Util._rows.cells;
    
// 用來存取可拖拽的對象
    Util.dragArray  =   new  Array();
    
var  counter  =   0 ;
    
for  ( var  i  =   0 ; i  <  Util.column.length; i ++ ) {
        
// 搜索所有的column
         var  ele  =  Util.column[i];
        
for  ( var  j  =   0 ; j  <  ele.childNodes.length; j ++ ) {
            
// 搜索每一column裏面的所有element
             var  ele1  =  ele.childNodes[j];
            
// 如果是div就把它初始化爲一個draggable對象
             if  (ele1.tagName  ==   " DIV " ) {
                Util.dragArray[counter] 
=   new  draggable(ele1);
                counter
++ ;
            }
        }
    }
};

// google的頁面裏可以拖動的部分的id是"t_1"
//
掛載到onload,載入完畢執行。不過實際上google沒有用onload。
//
而是寫在頁面最下面,異曲同工吧,也許直接寫在頁面是種怪癖,或者也有可能是兼容性考慮。

// 請將下面兩條被註釋掉的代碼加,到你自己下載的一個google ig頁面裏面,把裏面的所有其餘script刪除,掛上這個js也可以拖拽了,哈哈
//
_table=document.getElementById("t_1");
//
window.onload = _IG_initDrag(_table);

// 其實看懂這些代碼對學習javascript很有益,希望對大家能有幫助
 原文URL:http://www.blogjava.net/iamtin/archive/2006/04/27/43668.html
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章