這兩天太忙了,沒時間寫B(tài)log,業(yè)余時間又扒了一次google的拖拽,對比了netvibes的,差不多,讓后就寫了個注釋,順便學習。
google用了混淆,js里面的命名比較暈。我花了些時間把他們翻譯成容易看懂的命名,并且作了注釋,希望對大家有幫助。
可以這里下載我的例子:
http://www.tkk7.com/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以內的絕對坐標
//
后面一個參數(shù)如果是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(參數(shù)中的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動態(tài)生成
getGhostElement?
=
?
function
?()?{
????
if
?(
!
ghostElement)?{
????????ghostElement?
=
?document.createElement(
"
DIV
"
);
????????ghostElement.className?
=
?
"
modbox
"
;
????????ghostElement.backgroundColor?
=
?
""
;
????????ghostElement.style.border?
=
?
"
2px?dashed?#aaa
"
;
????????ghostElement.innerHTML?
=
?
"
"
;
????}
????
return
?ghostElement;
};
//
初始化可以拖拽的Element的函數(shù),與拖拽無關的我去掉了
function
?draggable(el)?{
????
//
公用的開始拖拽的函數(shù)
????
this
._dragStart?
=
?start_Drag;
????
//
公用的正在拖拽的函數(shù)
????
this
._drag?
=
?when_Drag;
????
//
公用的拖拽結束的函數(shù)
????
this
._dragEnd?
=
?end_Drag;
????
//
這個函數(shù)主要用來進行拖拽結束后的dom處理
????
this
._afterDrag?
=
?after_Drag;
????
//
是否正在被拖動,一開始當然沒有被拖動
????
this
.isDragging?
=
?
false
;
????
//
將這個Element的this指針注冊在elm這個變量里面,方便在自己的上下文以外調用自己的函數(shù)等,很常用的方法
????
this
.elm?
=
?el;
????
//
觸發(fā)拖拽的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
"
;
????????
//
將函數(shù)綁定到header和element的this上,參照那個函數(shù)的說明
????????Drag.init(
this
.header,?
this
.elm);
????????
//
下面三個語句將寫好的三個函數(shù)綁定給這個elemnt的三個函數(shù)鉤子上,也就實現(xiàn)了element從draggable繼承可拖拽的函數(shù)
????????
this
.elm.onDragStart?
=
?Util.bindFunction(
this
,?
"
_dragStart
"
);
????????
this
.elm.onDrag?
=
?Util.bindFunction(
this
,?
"
_drag
"
);
????????
this
.elm.onDragEnd?
=
?Util.bindFunction(
this
,?
"
_dragEnd
"
);
????}
};
//
下面就是draggable里面用到的那4個function
//
公用的開始拖拽的函數(shù)
function
?start_Drag()?{
????
//
重置坐標,實現(xiàn)拖拽以后自己的位置馬上會被填充的效果
????Util.re_calcOff(
this
);
????
//
記錄原先的鄰居節(jié)點,用來對比是否被移動到新的位置
????
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
);
????
//
防止閃爍,現(xiàn)隱藏
????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實現(xiàn)絕對坐標定位,這就是先前計算坐標的作用,不讓這個模型亂跑,要從開始拖動的地方開始移動
????
this
.elm.style.left?
=
?offLeft?
+
?
"
px
"
;
????
this
.elm.style.top?
=
?offTop?
+
?
"
px
"
;
????
//
坐標設定完畢,可以顯示了,這樣就不會閃爍了
????Util.show();
????
//
這里本來有個ig_d.G,沒搞明白干什么用的,不過沒有也可以用,誰知道麻煩告訴我一聲,不好意思
????
//
還沒有開始拖拽,這里做個記號
????
this
.isDragging?
=
?
false
;
????
return
?
false
;
};
//
在拖拽時的相應函數(shù),由于綁定到鼠標的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
));
????????
//
自己已經(jīng)浮動了,所以不計算自己的
????????
if
?(ele?
==
?
this
)?{
????????????
continue
;
????????}
????????
//
如果計算失敗繼續(xù)循環(huán)
????????
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的現(xiàn)實問題,要隱藏/顯示后才能刷新出變化
????????????document.body.style.display?
=
?
"
none
"
;
????????????document.body.style.display?
=
?
""
;
????????}
????}
};
//
拖拽完畢
function
?end_Drag()?{
????
//
拖拽完畢后執(zhí)行后面的鉤子,執(zhí)行after_Drag(),如果布局發(fā)生了變動了就記錄到遠程服務器,保存你拖拽后新的布局順序
????
if
?(
this
._afterDrag())?{
????????
//
remote?call?to?save?the?change
????}
????
return
?
true
;
};
//
拖拽后的執(zhí)行鉤子
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();
????
//
如果現(xiàn)在的鄰居不是原來的鄰居了
????
if
?(ele.nextSibling?
!=
?
this
.origNextSibling)?{
????????
//
把被拖拽的這個節(jié)點插到灰框的前面
????????ele.parentNode.insertBefore(
this
.elm,?ele.nextSibling);
????????
//
標明被拖拽了新的地方
????????returnValue?
=
?
true
;
????}
????
//
移除灰框,這是這個灰框的生命周期應該就結束了
????ele.parentNode.removeChild(ele);
????
//
修改完畢,顯示
????Util.show();
????
if
?(Util.isOpera)?{
????????
//
Opera的現(xiàn)實問題,要隱藏/顯示后才能刷新出變化
????????document.body.style.display?
=
?
"
none
"
;
????????document.body.style.display?
=
?
""
;
????}
????
return
?returnValue;
};
//
可拖拽Element的原形,用來將event綁定到各個鉤子,這部分市比較通用的,netvibes也是基本完全相同的實現(xiàn)
//
這部分推薦看dindin的這個,也會幫助理解,http://www.jroller.com/page/dindin/?anchor=pro_javascript_12
var
?Drag?
=
?{
????
//
對這個element的引用,一次只能拖拽一個Element
????obj:
null
,?
????
//
element是被拖拽的對象的引用,elementHeader就是鼠標可以拖拽的區(qū)域
????init:
function
?(elementHeader,?element)?{
????????
//
將start綁定到onmousedown事件,按下鼠標觸發(fā)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被調用后才幫定到實際的函數(shù),可以參照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
;
????????}
????????
//
參照這個函數(shù)的解釋,掛上開始拖拽的鉤子
????????element.onDragStart();
????????
//
記錄鼠標坐標
????????element.lastMouseX?
=
?event.clientX;
????????element.lastMouseY?
=
?event.clientY;
????????
//
將Global的event綁定到被拖動的element上面來
????????document.onmouseup?
=
?Drag.end;
????????document.onmousemove?
=
?Drag.drag;
????????
return
?
false
;
????},?
????
//
Element正在被拖動的函數(shù)
????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現(xiàn)在的坐標供下一次移動使用
????????element.lastMouseX?
=
?_clientY;
????????element.lastMouseY?
=
?_clientX;
????????
//
參照這個函數(shù)的解釋,掛接上Drag時的鉤子
????????element.onDrag(newX,?newY);
????????
return
?
false
;
????},
????
//
Element正在被釋放的函數(shù),停止拖拽
????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_;
????}
};
//
下面是初始化的函數(shù)了,看看上面這些東西怎么被調用
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,載入完畢執(zhí)行。不過實際上google沒有用onload。
//
而是寫在頁面最下面,異曲同工吧,也許直接寫在頁面是種怪癖,或者也有可能是兼容性考慮。
//
請將下面兩條被注釋掉的代碼加,到你自己下載的一個google?ig頁面里面,把里面的所有其余script刪除,掛上這個js也可以拖拽了,哈哈
//
_table=document.getElementById("t_1");
//
window.onload?=?_IG_initDrag(_table);
//
其實看懂這些代碼對學習javascript很有益,希望對大家能有幫助