既然說是入門,本文給出的函式都儘可能短小,但不失強大,併相容所有瀏覽器。要想拖動頁面上的一個元素,說白了就是讓它實現位移。CSS中最能體現這種思想的是絕對定位,因為絕對定位能使元素脫離原來的文件流,但原來的物理空間還保留著,這就不影響周圍的元素了。接著下來就是設定事件偵聽器,分別在mousedown,mousemove,mouseup繫結對應的回撥函式,一旦觸發這些事件,瀏覽器就會自動呼叫它們。我們分別把這些回撥函式命名為dragstart,drag與dragend。
- 在dragstart方法中,我們的工作取得滑鼠相對於事件源的距離。IE中,我們可以很輕鬆地用offsetX與offsetY實現,在firefox中我們要用layerX與layerY。其他瀏覽器大多數是牆頭草,兩個都實現,但Opera是IE那一方的。為了實現全面相容,就要繞點遠路,利用e.clientX - el.offsetLeft與e.clientY - el.offsetTop獲取它們。並在此方法中開始監聽mousemove與mouseup事件。
- 在drag方法,我們要將拖動的距離加到原來的top與left上,以實現位移。拖動過程,可能引發沿文字的被選中,我們需要清除文字。
- 在dragend方法,我們要解除安裝繫結事件,釋放記憶體。
為了共享方法,我們把它們都做成原型方法。
01.
var
Drag =
function
(id){
02.
this
.node = document.getElementById(id);
03.
this
.node.style.position =
"absolute"
04.
this
.node.me =
this
;
//儲存自身的引用
05.
this
.node.onmousedown =
this
.dragstart;
//監聽mousedown事件
06.
}
07.
Drag.prototype = {
08.
constructor:Drag,
09.
dragstart:
function
(e){
10.
var
e = e || window.event,
//獲得事件物件
11.
self =
this
.me,
//獲得拖動物件
12.
node = self.node;
//獲得拖動元素
13.
//滑鼠游標相對於事件源物件的座標
14.
node.offset_x = e.clientX - node.offsetLeft;
15.
node.offset_y = e.clientY - node.offsetTop;
16.
node.onmousemove = self.drag;
//監聽mousemove事件
17.
node.onmouseup = self.dragend;
//監聽mouseup事件
18.
},
19.
drag:
function
(e){
20.
var
e = e || window.event,
//獲得事件物件
21.
self =
this
.me,
//獲得拖動物件
22.
node = self.node;
//獲得拖動元素
23.
node.style.cursor =
"pointer"
;
24.
//將拖動的距離加再在原先的left與top上,以實現位移
25.
!+
"\v1"
? document.selection.empty() : window.getSelection().removeAllRanges();
26.
node.style.left = e.clientX - node.offset_x +
"px"
;
27.
node.style.top = e.clientY - node.offset_y +
"px"
;
28.
node.onmouseup = self.dragend;
//監聽mouseup事件
29.
},
30.
dragend:
function
(){
31.
var
self =
this
.me,
//獲得拖動物件
32.
node = self.node;
//獲得拖動元素
33.
node.onmousemove =
null
;
34.
node.onmouseup =
null
;
35.
}
36.
}
執行程式碼
現在我們的類就可以運作了,但正如你們所看到的那樣,當滑鼠拖動太快會出現滑鼠移出div的情況。這是因為移動得越快,位移的距離就越大,拖動元素一下子從我們的滑鼠溜走了,就無法呼叫mouseup事件。在IE中我們可以利用setCapture()來補救,但一旦某個元素呼叫setCapture(),文件中所有後續的滑鼠事件都會在冒泡之前傳到該元素,直到呼叫了releaseCapture()。換言之,在完成這些滑鼠事件之前,它是不執行其他事件,一直佔著執行緒,於是出現了我們的游標離開拖動元素的上方也能拖動元素的怪異現象。
<!doctype html> <html dir="ltr" lang="zh-CN"> <head> <meta charset="utf-8"/> <meta http-equiv="X-UA-Compatible" content="IE=8"> <style type="text/css"> #drag {width:100px;height:100px;background:red;z-index:200;} </style> <script type="text/javascript"> var Drag = function(id){ this.node = document.getElementById(id); this.node.style.position = "absolute" this.node.me = this;//儲存自身的引用 this.node.onmousedown = this.dragstart;//監聽mousedown事件 } Drag.prototype = { constructor:Drag, dragstart:function(e){ var e = e || window.event,//獲得事件物件 self = this.me,//獲得拖動物件 node = self.node;//獲得拖動元素 //滑鼠游標相對於事件源物件的座標 node.offset_x = e.clientX - node.offsetLeft; node.offset_y = e.clientY - node.offsetTop; node.onmousemove = self.drag;//監聽mousemove事件 node.onmouseup = self.dragend;//監聽mouseup事件 }, drag:function(e){ var e = e || window.event,//獲得事件物件 self = this.me,//獲得拖動物件 node = self.node;//獲得拖動元素 node.style.cursor = "pointer"; //將拖動的距離加再在原先的left與top上,以實現位移 !+"\v1"? document.selection.empty() : window.getSelection().removeAllRanges(); if(!+"\v1"){node.setCapture(); } node.style.left = e.clientX - node.offset_x + "px"; node.style.top = e.clientY - node.offset_y + "px"; node.onmouseup = self.dragend;//監聽mouseup事件 }, dragend:function(){ var self = this.me,//獲得拖動物件 node = self.node;//獲得拖動元素 node.onmousemove = null; node.onmouseup = null; } } window.onload = function(){ new Drag("drag"); }; </script> <title>Drag and Drop</title> </head> <body> <p>拖動時可能被選中的文字……………………</p> <div id="drag"></div> <table class="filement_table"> <thead> <tr> <th>nodeType</th> <th> </th> <th> nodeName </th> <th> nodeValue </th> <th> attributes </th> </tr> </thead> <tbody> <tr> <td>1</td> <td>Element</td> <td>tagName大寫</td> <td>null</td> <td>NamedNodeMap</td> </tr> <tr> <td>2</td> <td>Attr</td> <td>name of attribute小寫</td> <td>value of attribute</td> <td>null</td> </tr> <tr> <td>3</td> <td>Text</td> <td>#text</td> <td>content of the text node</td> <td>null</td> </tr> <tr> <td>4</td> <td>CDATASection</td> <td>#cdata-section</td> <td>content of the CDATA Section</td> <td>null</td> </tr> <tr> <td>5</td> <td>EntityReference</td> <td>name of entity referenced</td> <td>null</td> <td>null</td> </tr> <tr> <td>6</td> <td>Entity</td> <td>entity name</td> <td>null</td> <td>null</td> </tr> <tr> <td>7</td> <td>ProcessingInstruction</td> <td>target</td> <td>entire content excluding the target</td> <td>null</td> </tr> <tr> <td>8</td> <td>Comment</td> <td>#comment</td> <td>content of the comment</td> <td>null</td> </tr> <tr> <td>9</td> <td>Document</td> <td>#document</td> <td>null</td> <td>null</td> </tr> <tr> <td>10</td> <td>DocumentType</td> <td>document type name</td> <td>null</td> <td>null</td> </tr> <tr> <td>11</td> <td>DocumentFragment</td> <td>#document-fragment</td> <td>null</td> <td>null</td> </tr> <tr> <td>12</td> <td>Notation</td> <td>notation name</td> <td>null</td> <td>null</td> </tr> </tbody> </table> </body> </html>執行程式碼
你在拖動塊上點一下,然後再到拖動塊外面點一下,就可以實現"隔空拖動"的神奇效果了!(當然只限IE)
由於滑鼠事件一直接著執行緒,所以在我們不用的時候,一定要releaseCapture()來解放它。
在firefox中我們可以使用window.captureEvents(),火狐說這方法已經廢棄,但我怎麼在各標準瀏覽器中運作良好呢?!不過不管怎麼樣,來來回回要設定捕獲與取消捕獲非常麻煩與吃記憶體,我們需要轉換思路。因為如果滑鼠離開拖動元素上方,我們的繫結函式就無法運作,要是把它們繫結在document上呢,滑鼠就無論在何處都能監聽拖動元素。但觸發拖動的onmousedown事件我們還保留在拖動元素上,這事件不會因為不執行就引起差錯之虞。不過,由於繫結物件一變,我們要在這些事件中獲得拖動物件的引用的難度就陡然加大,這裡我就直接把它們做成建構函式內的私有函式吧。
01.
var
Drag =
function
(id){
02.
var
el = document.getElementById(id);
03.
el.style.position =
"absolute"
;
04.
var
drag =
function
(e) {
05.
e = e || window.event;
06.
el.style.cursor =
"pointer"
;
07.
!+
"\v1"
? document.selection.empty() : window.getSelection().removeAllRanges();
08.
el.style.left = e.clientX - el.offset_x +
"px"
;
09.
el.style.top = e.clientY - el.offset_y +
"px"
;
10.
el.innerHTML = parseInt(el.style.left,10)+
"X"
+parseInt(el.style.top,10);
11.
}
12.
13.
var
dragend =
function
(){
14.
document.onmouseup =
null
;
15.
document.onmousemove =
null
;
16.
}
17.
18.
var
dragstart =
function
(e){
19.
e = e || window.event;
20.
el.offset_x = e.clientX - el.offsetLeft;
21.
el.offset_y = e.clientY - el.offsetTop;
22.
document.onmouseup = dragend;
23.
document.onmousemove = drag;
24.
return
false
;
25.
}
26.
el.onmousedown = dragstart;
27.
}
執行程式碼
進一步改進,不使用mouseup事件,這樣就減少了錯過mouseup事件帶來的滑鼠粘著卡殼的問題。但由於標準瀏覽器不會自動切換e.button和e.which的值,我被迫動用一個功能鍵Shirt來停止滑鼠拖拽。
01.
var
Drag =
function
(id){
02.
var
el = document.getElementById(id);
03.
el.style.position =
"absolute"
;
04.
var
drag =
function
(e){
05.
var
e = e || window.event,
06.
button = e.button || e.which;
07.
if
(button == 1 && e.shiftKey ==
false
){
08.
el.style.cursor =
"pointer"
;
09.
!+
"\v1"
? document.selection.empty() : window.getSelection().removeAllRanges();
10.
el.style.left = e.clientX - el.offset_x +
"px"
;
11.
el.style.top = e.clientY - el.offset_y +
"px"
;
12.
el.innerHTML = parseInt(el.style.left,10)+
" x "
+parseInt(el.style.top,10);
13.
}
else
{
14.
document.onmousemove =
null
;
15.
}
16.
}
17.
var
dragstart =
function
(e){
18.
e = e || window.event;
19.
el.offset_x = e.clientX - el.offsetLeft;
20.
el.offset_y = e.clientY - el.offsetTop;
21.
el.style.zIndex = ++Drag.z;
22.
document.onmousemove = drag;
23.
return
false
;
24.
}
25.
Drag.z = 999;
26.
el.onmousedown = dragstart;
27.
}
執行程式碼
雖然不繫結mouseup的確在IE爽到high起,但在火狐等瀏覽要多按一個鍵才能終止拖動,怎麼說也對使用者體驗造成影響,因此當一種知識學學就算了。我們還是選擇方案2。
接著下來我們為方案擴充套件一下功能。首先範圍拖動,就是存在一個讓它在上面拖動的容器。如果存在容器,我們就取得其容器的四個點的座標,與拖動元素的四個點的座標比較,從而修正top與left。由於我已經實現了getCoords函式,取得頁面上某一點的座標易如反掌。同時這樣做,我們就不用把此容器設定成offsetParent。總之,多一事不如少一事。
01.
var
getCoords =
function
(el){
02.
var
box = el.getBoundingClientRect(),
03.
doc = el.ownerDocument,
04.
body = doc.body,
05.
html = doc.documentElement,
06.
clientTop = html.clientTop || body.clientTop || 0,
07.
clientLeft = html.clientLeft || body.clientLeft || 0,
08.
top = box.top + (self.pageYOffset || html.scrollTop || body.scrollTop ) - clientTop,
09.
left = box.left + (self.pageXOffset || html.scrollLeft || body.scrollLeft) - clientLeft
10.
return
{
'top'
: top,
'left'
: left };
11.
};
接著我們要取得容器的四個點的座標:
1.
_cCoords = getCoords(container),
2.
_cLeft = _cCoords.left,
3.
_cTop = _cCoords.top,
4.
_cRight = _cLeft + container.clientWidth,
5.
_cBottom = _cTop + container.clientHeight;
接著是拖動元素的四個點的座標:
1.
var
_left = el.offsetLeft,
2.
_top = el.offsetTop,
3.
_right = _left + el.offsetWidth,
4.
_bottom = _top + el.offsetHeight,
但是這樣取得的是沒有拖動前的座標,拖動時的座標在上面的方案2也已給出了:
1.
var
_left = e.clientX - el.offset_x,
2.
_top = e.clientY - el.offset_y,
修正座標很簡單,如果拖動元素在左邊或上邊超出容器,就讓拖動元素的top與left取容器的_cLeft或_cTop值,如果從右邊超出容器,我們用_cRight減去容器的寬度再賦給拖動元素的top,下邊的處理相仿。
01.
if
(_left < _cLeft){
02.
_left = _cLeft
03.
}
04.
if
(_top < _cTop){
05.
_top = _cTop
06.
}
07.
if
(_right > _cRight){
08.
_left = _cRight - el.offsetWidth;
09.
}
10.
if
(_bottom > _cBottom){
11.
_top = _cBottom - el.offsetHeight;
12.
}
13.
el.style.left = _left +
"px"
;
14.
el.style.top = _top +
"px"
;
執行程式碼
水平鎖定與垂直鎖直也是兩個很常用的功能,我們也來實現它。原理很簡單,在開始拖動時就把top與left儲存起來,待到拖動時再賦給它就行了。程式碼請直接看執行框的程式碼:
<!doctype html> <html dir="ltr" lang="zh-CN"> <head> <meta charset="utf-8"/> <meta http-equiv="X-UA-Compatible" content="IE=8"> <style type="text/css"> .drag {width:100px;height:100px;z-index:200;} #drag1 {background:red} #drag2 {background:#E8D098;} #drag3 {background:#fff;} #drag4 {background:#E8FFE8;} #drag5 {background:#ff0;} #drag6 {background:#66c;} #parent {width:300px;height:300px;background:blue;} </style> <script type="text/javascript"> var getCoords = function(el){ var box = el.getBoundingClientRect(), doc = el.ownerDocument, body = doc.body, html = doc.documentElement, clientTop = html.clientTop || body.clientTop || 0, clientLeft = html.clientLeft || body.clientLeft || 0, top = box.top + (self.pageYOffset || html.scrollTop || body.scrollTop ) - clientTop, left = box.left + (self.pageXOffset || html.scrollLeft || body.scrollLeft) - clientLeft return { 'top': top, 'left': left }; }; var Drag = function(id){ var el = document.getElementById(id), options = arguments[1] || {}, container = options.container || document.documentElement, limit = false || options.limit, lockX = false || options.lockX, lockY = false || options.lockY; el.style.position = "absolute"; var drag = function(e) { e = e || window.event; el.style.cursor = "pointer"; !+"\v1"? document.selection.empty() : window.getSelection().removeAllRanges(); var _left = e.clientX - el.offset_x, _top = e.clientY - el.offset_y; if(limit){ var _right = _left + el.offsetWidth, _bottom = _top + el.offsetHeight, _cCoords = getCoords(container), _cLeft = _cCoords.left, _cTop = _cCoords.top, _cRight = _cLeft + container.clientWidth, _cBottom = _cTop + container.clientHeight; _left = Math.max(_left, _cLeft); _top = Math.max(_top, _cTop); if(_right > _cRight){ _left = _cRight - el.offsetWidth; } if(_bottom > _cBottom){ _top = _cBottom - el.offsetHeight; } } if(lockX){ _left = el.lockX; } if(lockY){ _top = el.lockY; } el.style.left = _left + "px"; el.style.top = _top + "px"; el.innerHTML = parseInt(el.style.left,10)+ " x "+parseInt(el.style.top,10); } var dragend = function(){ document.onmouseup = null; document.onmousemove = null; } var dragstart = function(e){ e = e || window.event; if(lockX){ el.lockX = getCoords(el).left; } if(lockY){ el.lockY = getCoords(el).top; } if(/a/[-1]=='a'){ el.offset_x = e.layerX el.offset_y = e.layerY }else{ el.offset_x = e.offsetX el.offset_y = e.offsetY } document.onmouseup = dragend; document.onmousemove = drag; el.style.zIndex = ++Drag.z; return false; } Drag.z = 999; el.onmousedown = dragstart; } window.onload = function(){ var p = document.getElementById("parent"); new Drag("drag1",{container:p,limit:true,lockX:true}); new Drag("drag2",{container:p,limit:true,lockY:true}); new Drag("drag3",{container:p,limit:true}); new Drag("drag4",{container:p,limit:true}); new Drag("drag5",{container:p,limit:true}); new Drag("drag6",{container:p,limit:true}); }; </script> <title>拖動</title> </head> <body id="body"> <p>拖動時可能被選中的文字……………………</p> <div id="parent"> <div id="drag1" class="drag"></div> <div id="drag2" class="drag"></div> <div id="drag3" class="drag"></div> <div id="drag4" class="drag"></div> <div id="drag5" class="drag"></div> <div id="drag6" class="drag"></div> </div> </body> </html>執行程式碼