這兩天太忙了,沒時(shí)間寫B(tài)log,業(yè)余時(shí)間又扒了一次google的拖拽,對比了netvibes的,差不多,讓后就寫了個(gè)注釋,順便學(xué)習(xí)。
google用了混淆,js里面的命名比較暈。我花了些時(shí)間把他們翻譯成容易看懂的命名,并且作了注釋,希望對大家有幫助。
可以這里下載我的例子:
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
;
//
獲取一個(gè)element的offset信息,其實(shí)就是相對于Body的padding以內(nèi)的絕對坐標(biāo)
//
后面一個(gè)參數(shù)如果是true則獲取offsetLeft,false則是offsetTop
//
關(guān)于offset、style、client等坐標(biāo)的定義參考dindin的這個(gè)帖子: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;
};
//
將一個(gè)function(參數(shù)中的funcName是這個(gè)fuction的名字)綁定到一個(gè)element上,并且以這個(gè)element的上下文運(yùn)行,其實(shí)是一種繼承,這個(gè)可以google些文章看看
Util.bindFunction?
=
?
function
?(el,?fucName)?{
????
return
?
function
?()?{
????????
return
?el[fucName].apply(el,?arguments);
????};
};
//
重新計(jì)算所有的可以拖拽的element的坐標(biāo),對同一個(gè)column下面的可拖拽圖層重新計(jì)算它們的高度而得出新的坐標(biāo),防止遮疊
//
計(jì)算出來的坐標(biāo)記錄在pagePosLeft和pagePosTop兩個(gè)屬性里面
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中間那個(gè)table,也就是拖拽的容器,配合show一般就是刷新用,解決一些瀏覽器的怪癖
Util.hide?
=
?
function
?()?{
????Util.rootElement.style.display?
=
?
"
none
"
;
};
//
顯示Google?Ig中間那個(gè)table,解釋同上
Util.show?
=
?
function
?()?{
????Util.rootElement.style.display?
=
?
""
;
};
//
移動(dòng)時(shí)顯示的占位虛線框
ghostElement?
=
?
null
;
//
獲取這個(gè)虛線框,通過dom動(dòng)態(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ù),與拖拽無關(guān)的我去掉了
function
?draggable(el)?{
????
//
公用的開始拖拽的函數(shù)
????
this
._dragStart?
=
?start_Drag;
????
//
公用的正在拖拽的函數(shù)
????
this
._drag?
=
?when_Drag;
????
//
公用的拖拽結(jié)束的函數(shù)
????
this
._dragEnd?
=
?end_Drag;
????
//
這個(gè)函數(shù)主要用來進(jìn)行拖拽結(jié)束后的dom處理
????
this
._afterDrag?
=
?after_Drag;
????
//
是否正在被拖動(dòng),一開始當(dāng)然沒有被拖動(dòng)
????
this
.isDragging?
=
?
false
;
????
//
將這個(gè)Element的this指針注冊在elm這個(gè)變量里面,方便在自己的上下文以外調(diào)用自己的函數(shù)等,很常用的方法
????
this
.elm?
=
?el;
????
//
觸發(fā)拖拽的Element,在這里就是這個(gè)div上顯示標(biāo)題的那個(gè)div
????
this
.header?
=
?document.getElementById(el.id?
+
?
"
_h
"
);
????
//
對于有iframe的element拖拽不同,這里檢測一下并記錄
????
this
.hasIFrame?
=
?
this
.elm.getElementsByTagName(
"
IFRAME
"
).length?
>
?
0
;
????
//
如果找到了header就綁定drag相關(guān)的event
????
if
?(
this
.header)?{
????????
//
拖拽時(shí)的叉子鼠標(biāo)指針
????????
this
.header.style.cursor?
=
?
"
move
"
;
????????
//
將函數(shù)綁定到header和element的this上,參照那個(gè)函數(shù)的說明
????????Drag.init(
this
.header,?
this
.elm);
????????
//
下面三個(gè)語句將寫好的三個(gè)函數(shù)綁定給這個(gè)elemnt的三個(gè)函數(shù)鉤子上,也就實(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個(gè)function
//
公用的開始拖拽的函數(shù)
function
?start_Drag()?{
????
//
重置坐標(biāo),實(shí)現(xiàn)拖拽以后自己的位置馬上會(huì)被填充的效果
????Util.re_calcOff(
this
);
????
//
記錄原先的鄰居節(jié)點(diǎn),用來對比是否被移動(dòng)到新的位置
????
this
.origNextSibling?
=
?
this
.elm.nextSibling;
????
//
獲取移動(dòng)的時(shí)候那個(gè)灰色的虛線框
????
var
?_ghostElement?
=
?getGhostElement();
????
//
獲取正在移動(dòng)的這個(gè)對象的高度
????
var
?offH?
=
?
this
.elm.offsetHeight;
????
if
?(Util.isGecko)?{
????????
//
修正gecko引擎的怪癖吧
????????offH?
-=
?parseInt(_ghostElement.style.borderTopWidth)?
*
?
2
;
????}
????
//
獲取正在移動(dòng)的這個(gè)對象的寬度
????
var
?offW?
=
?
this
.elm.offsetWidth;
????
//
獲取left和top的坐標(biāo)
????
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
"
;
????
//
將那個(gè)灰框設(shè)定得與正在拖動(dòng)的對象一樣高,比較形象
????_ghostElement.style.height?
=
?offH?
+
?
"
px
"
;
????
//
把灰框放到這個(gè)對象原先的位置上
????
this
.elm.parentNode.insertBefore(_ghostElement,?
this
.elm.nextSibling);
????
//
由于要拖動(dòng)必須將被拖動(dòng)的對象從原先的盒子模型里面抽出來,所以設(shè)定position為absolute,這個(gè)可以參考一下css布局方面的知識
????
this
.elm.style.position?
=
?
"
absolute
"
;
????
//
設(shè)置zIndex,讓它處在最前面一層,當(dāng)然其實(shí)zIndex=100是讓它很靠前,如果頁面里有zIndex>100的,那……
????
this
.elm.style.zIndex?
=
?
100
;
????
//
由于position=absolute了,所以left和top實(shí)現(xiàn)絕對坐標(biāo)定位,這就是先前計(jì)算坐標(biāo)的作用,不讓這個(gè)模型亂跑,要從開始拖動(dòng)的地方開始移動(dòng)
????
this
.elm.style.left?
=
?offLeft?
+
?
"
px
"
;
????
this
.elm.style.top?
=
?offTop?
+
?
"
px
"
;
????
//
坐標(biāo)設(shè)定完畢,可以顯示了,這樣就不會(huì)閃爍了
????Util.show();
????
//
這里本來有個(gè)ig_d.G,沒搞明白干什么用的,不過沒有也可以用,誰知道麻煩告訴我一聲,不好意思
????
//
還沒有開始拖拽,這里做個(gè)記號
????
this
.isDragging?
=
?
false
;
????
return
?
false
;
};
//
在拖拽時(shí)的相應(yīng)函數(shù),由于綁定到鼠標(biāo)的move這個(gè)event上,所以會(huì)傳入鼠標(biāo)的坐標(biāo)clientX,?clientY
function
?when_Drag(clientX,?clientY)?{
????
//
剛開始拖拽的時(shí)候?qū)D層變透明,并標(biāo)記為正在被拖拽
????
if
?(
!
this
.isDragging)?{
????????
this
.elm.style.filter?
=
?
"
alpha(opacity=70)
"
;
????????
this
.elm.style.opacity?
=
?
0.7
;
????????
this
.isDragging?
=
?
true
;
????}
????
//
被拖拽到的新的column(當(dāng)然也可以是原來那個(gè))
????
var
?found?
=
?
null
;
????
//
最大的距離,可能是防止溢出或者什么bug
????
var
?max_distance?
=
?
100000000
;
????
//
遍歷所有的可拖拽的element,尋找離當(dāng)前鼠標(biāo)坐標(biāo)最近的那個(gè)可拖拽元素,以便后面插入
????
for
?(
var
?i?
=
?
0
;?i?
<
?Util.dragArray.length;?i
++
)?{
????????
var
?ele?
=
?Util.dragArray[i];
????????
//
利用勾股定理計(jì)算鼠標(biāo)到遍歷到的這個(gè)元素的距離
????????
var
?distance?
=
?Math.sqrt(Math.pow(clientX?
-
?ele.elm.pagePosLeft,?
2
)?
+
?Math.pow(clientY?
-
?ele.elm.pagePosTop,?
2
));
????????
//
自己已經(jīng)浮動(dòng)了,所以不計(jì)算自己的
????????
if
?(ele?
==
?
this
)?{
????????????
continue
;
????????}
????????
//
如果計(jì)算失敗繼續(xù)循環(huán)
????????
if
?(isNaN(distance))?{
????????????
continue
;
????????}
????????
//
如果更小,記錄下這個(gè)距離,并將它作為found
????????
if
?(distance?
<
?max_distance)?{
????????????max_distance?
=
?distance;
????????????found?
=
?ele;
????????}
????}
????
//
準(zhǔn)備讓灰框落腳
????
var
?_ghostElement?
=
?getGhostElement();
????
//
如果找到了另外的落腳點(diǎn)
????
if
?(found?
!=
?
null
?
&&
?_ghostElement.nextSibling?
!=
?found.elm)?{
????????
//
找到落腳點(diǎn)就先把灰框插進(jìn)去,這就是我們看到的那個(gè)灰框停靠的特效,有點(diǎn)像吸附的感覺,哈哈
????????found.elm.parentNode.insertBefore(_ghostElement,?found.elm);
????????
if
?(Util.isOpera)?{
????????????
//
Opera的現(xiàn)實(shí)問題,要隱藏/顯示后才能刷新出變化
????????????document.body.style.display?
=
?
"
none
"
;
????????????document.body.style.display?
=
?
""
;
????????}
????}
};
//
拖拽完畢
function
?end_Drag()?{
????
//
拖拽完畢后執(zhí)行后面的鉤子,執(zhí)行after_Drag(),如果布局發(fā)生了變動(dòng)了就記錄到遠(yuǎn)程服務(wù)器,保存你拖拽后新的布局順序
????
if
?(
this
._afterDrag())?{
????????
//
remote?call?to?save?the?change
????}
????
return
?
true
;
};
//
拖拽后的執(zhí)行鉤子
function
?after_Drag()?{
????
var
?returnValue?
=
?
false
;
????
//
防止閃爍
????Util.hide();
????
//
把拖拽時(shí)的position=absolute和相關(guān)的那些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)?{
????????
//
把被拖拽的這個(gè)節(jié)點(diǎn)插到灰框的前面
????????ele.parentNode.insertBefore(
this
.elm,?ele.nextSibling);
????????
//
標(biāo)明被拖拽了新的地方
????????returnValue?
=
?
true
;
????}
????
//
移除灰框,這是這個(gè)灰框的生命周期應(yīng)該就結(jié)束了
????ele.parentNode.removeChild(ele);
????
//
修改完畢,顯示
????Util.show();
????
if
?(Util.isOpera)?{
????????
//
Opera的現(xiàn)實(shí)問題,要隱藏/顯示后才能刷新出變化
????????document.body.style.display?
=
?
"
none
"
;
????????document.body.style.display?
=
?
""
;
????}
????
return
?returnValue;
};
//
可拖拽Element的原形,用來將event綁定到各個(gè)鉤子,這部分市比較通用的,netvibes也是基本完全相同的實(shí)現(xiàn)
//
這部分推薦看dindin的這個(gè),也會(huì)幫助理解,http://www.jroller.com/page/dindin/?anchor=pro_javascript_12
var
?Drag?
=
?{
????
//
對這個(gè)element的引用,一次只能拖拽一個(gè)Element
????obj:
null
,?
????
//
element是被拖拽的對象的引用,elementHeader就是鼠標(biāo)可以拖拽的區(qū)域
????init:
function
?(elementHeader,?element)?{
????????
//
將start綁定到onmousedown事件,按下鼠標(biāo)觸發(fā)start
????????elementHeader.onmousedown?
=
?Drag.start;
????????
//
將element存到header的obj里面,方便header拖拽的時(shí)候引用
????????elementHeader.obj?
=
?element;
????????
//
初始化絕對的坐標(biāo),因?yàn)椴皇莗osition=absolute所以不會(huì)起什么作用,但是防止后面onDrag的時(shí)候parse出錯(cuò)了
????????
if
?(isNaN(parseInt(element.style.left)))?{
????????????element.style.left?
=
?
"
0px
"
;
????????}
????????
if
?(isNaN(parseInt(element.style.top)))?{
????????????element.style.top?
=
?
"
0px
"
;
????????}
????????
//
掛上空Function,初始化這幾個(gè)成員,在Drag.init被調(diào)用后才幫定到實(shí)際的函數(shù),可以參照draggable里面的內(nèi)容
????????element.onDragStart?
=
?
new
?Function();
????????element.onDragEnd?
=
?
new
?Function();
????????element.onDrag?
=
?
new
?Function();
????},
????
//
開始拖拽的綁定,綁定到鼠標(biāo)的移動(dòng)的event上
????start:
function
?(event)?{
????????
var
?element?
=
?Drag.obj?
=
?
this
.obj;
????????
//
解決不同瀏覽器的event模型不同的問題
????????event?
=
?Drag.fixE(event);
????????
//
看看是不是左鍵點(diǎn)擊
????????
if
?(event.which?
!=
?
1
)?{
????????????
//
除了左鍵都不起作用
????????????
return
?
true
;
????????}
????????
//
參照這個(gè)函數(shù)的解釋,掛上開始拖拽的鉤子
????????element.onDragStart();
????????
//
記錄鼠標(biāo)坐標(biāo)
????????element.lastMouseX?
=
?event.clientX;
????????element.lastMouseY?
=
?event.clientY;
????????
//
將Global的event綁定到被拖動(dòng)的element上面來
????????document.onmouseup?
=
?Drag.end;
????????document.onmousemove?
=
?Drag.drag;
????????
return
?
false
;
????},?
????
//
Element正在被拖動(dòng)的函數(shù)
????drag:
function
?(event)?{
????????
//
解決不同瀏覽器的event模型不同的問題
????????event?
=
?Drag.fixE(event);
????????
//
看看是不是左鍵點(diǎn)擊
????????
if
?(event.which?
==
?
0
)?{
????????????
//
除了左鍵都不起作用
????????????
return
?Drag.end();
????????}
????????
//
正在被拖動(dòng)的Element
????????
var
?element?
=
?Drag.obj;
????????
//
鼠標(biāo)坐標(biāo)
????????
var
?_clientX?
=
?event.clientY;
????????
var
?_clientY?
=
?event.clientX;
????????
//
如果鼠標(biāo)沒動(dòng)就什么都不作
????????
if
?(element.lastMouseX?
==
?_clientY?
&&
?element.lastMouseY?
==
?_clientX)?{
????????????
return
?
false
;
????????}
????????
//
剛才Element的坐標(biāo)
????????
var
?_lastX?
=
?parseInt(element.style.top);
????????
var
?_lastY?
=
?parseInt(element.style.left);
????????
//
新的坐標(biāo)
????????
var
?newX,?newY;
????????
//
計(jì)算新的坐標(biāo):原先的坐標(biāo)+鼠標(biāo)移動(dòng)的值差
????????newX?
=
?_lastY?
+
?_clientY?
-
?element.lastMouseX;
????????newY?
=
?_lastX?
+
?_clientX?
-
?element.lastMouseY;
????????
//
修改element的顯示坐標(biāo)
????????element.style.left?
=
?newX?
+
?
"
px
"
;
????????element.style.top?
=
?newY?
+
?
"
px
"
;
????????
//
記錄element現(xiàn)在的坐標(biāo)供下一次移動(dòng)使用
????????element.lastMouseX?
=
?_clientY;
????????element.lastMouseY?
=
?_clientX;
????????
//
參照這個(gè)函數(shù)的解釋,掛接上Drag時(shí)的鉤子
????????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ù)了,看看上面這些東西怎么被調(diào)用
var
?_IG_initDrag?
=
?
function
?(el)?{
????
//
column那個(gè)容器,在google里面就是那個(gè)table布局的tbody,netvibes用的<div>
????Util.rootElement?
=
?el;
????
//
這個(gè)tbody的行
????Util._rows?
=
?Util.rootElement.tBodies[
0
].rows[
0
];
????
//
列,google是3列,其實(shí)也可以更多
????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就把它初始化為一個(gè)draggable對象
????????????
if
?(ele1.tagName?
==
?
"
DIV
"
)?{
????????????????Util.dragArray[counter]?
=
?
new
?draggable(ele1);
????????????????counter
++
;
????????????}
????????}
????}
};
//
google的頁面里可以拖動(dòng)的部分的id是"t_1"
//
掛載到onload,載入完畢執(zhí)行。不過實(shí)際上google沒有用onload。
//
而是寫在頁面最下面,異曲同工吧,也許直接寫在頁面是種怪癖,或者也有可能是兼容性考慮。
//
請將下面兩條被注釋掉的代碼加,到你自己下載的一個(gè)google?ig頁面里面,把里面的所有其余script刪除,掛上這個(gè)js也可以拖拽了,哈哈
//
_table=document.getElementById("t_1");
//
window.onload?=?_IG_initDrag(_table);
//
其實(shí)看懂這些代碼對學(xué)習(xí)javascript很有益,希望對大家能有幫助