文章目錄
- 屬性
- 初始化一個元素
- 基本位置資訊
- 事件
- Mousedown
- Mousemove
- Mouseup
- 基本互動
- 事件
- 初始鍵盤代碼
- 通過鍵盤拖拽
這是一個簡單可用的拖拽代碼。用滑鼠和鍵盤都可以操作。
當樣本的box上的#連結處於活動狀態的時候(不論是用tab然後點擊enter或者使用滑鼠點擊)這個元素就能夠通過方向鍵拖拽。然後點擊enter或者Esc釋放。(可以隨意改變這些鍵。我不確定釋放鍵應該設定成為什麼所以enter和Esc都可以)
使用
1、複製文章後面的dragDrop對象。
2、複製我的addEventSimple和removeEventSimple函數,這裡需要。
3、設定keyHTML和keySpeed屬性(下面有解釋)。
4、確定你所要拖拽的元素都有位置屬性:absolute或者fixed。
5、把所有可拖拽的元素髮送到對象的initElement函數。可以發送一個對象或者對象ID的字串。例如:
dragDrop.initElement('test');
dragDrop.initElement(document.getElementById('test2'));
6、當元素被拖拽過後,代碼會自動添加dragged類。你可以添加一些CSS效果。
7、如果你想當使用者放開元素之後做一些事情,你可以給releaseElement添加自己的函數。
屬性
你需要設定兩個屬性。
keyHTML包含一個需要拖拽的元素的鍵盤能訪問到的連結的內容。為了保持HTML簡潔,這裡只添加一個有簡單樣式的類。你可以隨意構建你的HTML,但是要記住一點就是必須有一個連結讓鍵盤能夠訪問到,鍵盤使用者需要一個焦點來觸發拖拽事件。
keySpeed用來設定鍵盤拖拽的速度,每次按鍵移動多少像素。我喜歡設定為10,你也可以嘗試一下其他的值。
這裡還有7個屬性,但是都是在代碼內部的。初始化的時候都設定為undefined,然後相應的函數會設定他們。
拖拽對象
複製下面這個對象到你的頁面,不要忘了addEventSimple和removeEventSimple。
dragDrop = {keyHTML: '<a href="#" class="keyLink">#</a>',keySpeed: 10, // pixels per keypress eventinitialMouseX: undefined,initialMouseY: undefined,startX: undefined,startY: undefined,dXKeys: undefined,dYKeys: undefined,draggedObject: undefined,initElement: function (element) {if (typeof element == 'string')element = document.getElementById(element);element.onmousedown = dragDrop.startDragMouse;element.innerHTML += dragDrop.keyHTML;var links = element.getElementsByTagName('a');var lastLink = links[links.length-1];lastLink.relatedElement = element;lastLink.onclick = dragDrop.startDragKeys;},startDragMouse: function (e) {dragDrop.startDrag(this);var evt = e || window.event;dragDrop.initialMouseX = evt.clientX;dragDrop.initialMouseY = evt.clientY;addEventSimple(document,'mousemove',dragDrop.dragMouse);addEventSimple(document,'mouseup',dragDrop.releaseElement);return false;},startDragKeys: function () {dragDrop.startDrag(this.relatedElement);dragDrop.dXKeys = dragDrop.dYKeys = 0;addEventSimple(document,'keydown',dragDrop.dragKeys);addEventSimple(document,'keypress',dragDrop.switchKeyEvents);this.blur();return false;},startDrag: function (obj) {if (dragDrop.draggedObject)dragDrop.releaseElement();dragDrop.startX = obj.offsetLeft;dragDrop.startY = obj.offsetTop;dragDrop.draggedObject = obj;obj.className += ' dragged';},dragMouse: function (e) {var evt = e || window.event;var dX = evt.clientX - dragDrop.initialMouseX;var dY = evt.clientY - dragDrop.initialMouseY;dragDrop.setPosition(dX,dY);return false;},dragKeys: function(e) {var evt = e || window.event;var key = evt.keyCode;switch (key) {case 37:// leftcase 63234:dragDrop.dXKeys -= dragDrop.keySpeed;break;case 38:// upcase 63232:dragDrop.dYKeys -= dragDrop.keySpeed;break;case 39:// rightcase 63235:dragDrop.dXKeys += dragDrop.keySpeed;break;case 40:// downcase 63233:dragDrop.dYKeys += dragDrop.keySpeed;break;case 13: // entercase 27: // escapedragDrop.releaseElement();return false;default:return true;}dragDrop.setPosition(dragDrop.dXKeys,dragDrop.dYKeys);if (evt.preventDefault)evt.preventDefault();return false;},setPosition: function (dx,dy) {dragDrop.draggedObject.style.left = dragDrop.startX + dx + 'px';dragDrop.draggedObject.style.top = dragDrop.startY + dy + 'px';},switchKeyEvents: function () {// for Opera and Safari 1.3removeEventSimple(document,'keydown',dragDrop.dragKeys);removeEventSimple(document,'keypress',dragDrop.switchKeyEvents);addEventSimple(document,'keypress',dragDrop.dragKeys);},releaseElement: function() {removeEventSimple(document,'mousemove',dragDrop.dragMouse);removeEventSimple(document,'mouseup',dragDrop.releaseElement);removeEventSimple(document,'keypress',dragDrop.dragKeys);removeEventSimple(document,'keypress',dragDrop.switchKeyEvents);removeEventSimple(document,'keydown',dragDrop.dragKeys);dragDrop.draggedObject.className = dragDrop.draggedObject.className.replace(/dragged/,'');dragDrop.draggedObject = null;}}
拖拽是什麼拖拽是在螢幕上移動元素的一種方法。為了讓元素能夠移動,元素必須有position屬性:absolute或者fixed,這樣才能通過修改它的座標(style.top和style.left)讓它移動。
(理論上position:relative也可以,但是幾乎沒用。另外,那樣需要額外的資料來計算,這裡我沒有寫)
設定座標很簡單;找到需要設定的元素的座標是這個代碼比較難的部分。大多數代碼都是用來處理這個問題的。
另外,保持易用性也比較重要。傳統上通過滑鼠來拖拽一個元素是最好的辦法,但是也要考慮到沒有滑鼠的使用者,所以也要保證鍵盤的可用性。
基礎知識讓我們先來看看一些基礎知識
初始化一個元素每個拖拽代碼都從初始化元素開始。這個工作通過下面的函完成:
initElement: function (element) {if (typeof element == 'string')element = document.getElementById(element);element.onmousedown = dragDrop.startDragMouse;element.innerHTML += dragDrop.keyHTML;var links = element.getElementsByTagName('a');var lastLink = links[links.length-1];lastLink.relatedElement = element;lastLink.onclick = dragDrop.startDragKeys;},
如果函數接收到一個字串,那麼就會當做元素ID來處理。然後給這個元素設定一個onmousedown事件,用來開始滑鼠部分的代碼。注意這裡我使用的是傳統事件註冊方式;因為我希望this關鍵字能夠在startDragDrop裡起作用。
然後把使用者定義的keyHTML添加到元素上,我相信這個連結是用來觸發鍵盤事件的。然後為這個連結設定鍵盤的觸發程式。然後儲存主要元素在relatedElement裡面,我們後面需要。
現在代碼就等使用者動作了
基本位置資訊我打算使用下面的方法來:首先我會讀取拖拽元素的初始位置,儲存在startX和startY裡面。然後計算滑鼠移動的位置或者鍵盤控制下移動的位置來決定元素從初始位置移動的範圍。
startX和startY通過startDrag函數來設定,這個函數在滑鼠和鍵盤事件裡都會用到。
startDrag: function (obj) {if (dragDrop.draggedObject)dragDrop.releaseElement();dragDrop.startX = obj.offsetLeft;dragDrop.startY = obj.offsetTop;dragDrop.draggedObject = obj;obj.className += ' dragged';},
首先,如果元素處於拖拽狀態,那麼我們就釋放他(我們後面再講)。
然後函數會找到元素在起始位置的座標(offsetLeft和offsetTop),然後儲存在startX和startY以備後用。
然後在draggedObject裡面儲存一個對象的引用。然後給他添加dragged類,這樣就可以通過CSS來設定拖拽時候的樣式了
當使用者使用滑鼠或者鍵盤拖拽元素的時候,代碼的最複雜的部分就要跟蹤位置的變化。然後給出dX和dY(X和Y的變化)。然後加上startX和startY就是元素現在的位置。
下面的函數用來設定位置:
setPosition: function (dx,dy) {dragDrop.draggedObject.style.left = dragDrop.startX + dx + 'px';dragDrop.draggedObject.style.top = dragDrop.startY + dy + 'px';},
函數通過滑鼠和鍵盤的移動計算所得的dX和dY與初始位置,來設定元素的新位置。
這部分很簡單,複雜的地方就在於dx和dy的獲得,滑鼠部分和鍵盤部分的處理非常的不同,我們分別來看。
滑鼠部分的代碼滑鼠部分的計算方面比鍵盤的要複雜一些,但是在瀏覽器安全色性方面問題不大。所以我們從滑鼠部分開始。
事件首先我們來討論事件。很明顯的,在拖拽過程中需要mousedown,mousemove,mouseup用來完成選擇對象,拖拽,拖拽完成這幾個動作。
這一系列事件從需要拖拽元素的mousedown事件開始。所以所有的拖拽元素都需要這個事件來標明拖拽開始了。我們看到:
element.onmousedown = dragDrop.startDragMouse;
然而mousemove和mouseup事件不應該設定在元素上而應該設定在整個文檔上。因為使用者可能很快很瘋狂的移動滑鼠,然後丟失拖拽元素。如果設定在元素上,因為滑鼠不在元素上了所以可能會無法控制,這對於易用性來說不是好事。
如果我們再文檔上設定mousemove和mouseup,就沒有這個問題。不管滑鼠在哪,元素都會響應mousemove和mouseup。這個的易用性就很強。
另外你只能在拖拽開始以後再設定mousemove和mouseup,然後當使用者釋放元素之後刪除它們。這樣代碼很乾淨而且節省系統資源,因為mousemove對系統的消耗很大。
Mousedown當拖拽元素髮生mousedown事件的時候,startDragMouse函數就開始執行:
startDragMouse: function (e) {dragDrop.startDrag(this);var evt = e || window.event;dragDrop.initialMouseX = evt.clientX;dragDrop.initialMouseY = evt.clientY;addEventSimple(document,'mousemove',dragDrop.dragMouse);addEventSimple(document,'mouseup',dragDrop.releaseElement);return false;},
首先會執行我們之前討論過的startDrag。然後尋找滑鼠的座標然後儲存在initialMouseX和initialMouseY中。後面我們會把滑鼠位置跟這個比較。
最後會返回false,這個用來阻止預設滑鼠事件:選擇文本。我們不想再拖拽的時候有文本被選中,這很煩人。
然後給文檔設定mouseup和mousemove事件處理常式。因為有可能文檔有他自己的mouseup和mousemove事件處理常式,所以我使用我的addEventSimple函數防止原來的事件處理常式失效。
Mousemove現在,當使用者移動滑鼠的時候dragMouse函數就執行了。
dragMouse: function (e) {var evt = e || window.event;var dX = evt.clientX - dragDrop.initialMouseX;var dY = evt.clientY - dragDrop.initialMouseY;dragDrop.setPosition(dX,dY);return false;},
這個函數會讀取滑鼠現在的座標,然後減去之前的座標,把得到的dX和dY傳遞給sePosition。
然後通過返回false來阻止滑鼠選擇文本的預設屬性。
Mouseup當使用者鬆開滑鼠的時候,會調用releaseElement。我們後面討論。
鍵盤部分代碼現在我們開始更複雜的鍵盤部分代碼。不像滑鼠拖拽那樣,鍵盤拖拽並沒有一個標準。雖說基本的互動不是太複雜,但是最好還是簡要說明一下。
基本互動用來拖拽的鍵最好是方向鍵,這很簡單。
啟用和釋放元素是比較有技巧的,在這裡My Code還需要加強。
我覺得如果用鍵盤來啟用的話就應該使用一個我添加的額外的連結。這裡沒有太多選擇:因為連結能夠在所有的瀏覽器裡面獲得焦點(好吧,表單也可以,你也可是選擇複選框),而且把一個連結放置在可拖拽的元素裡面也是合乎邏輯的(你可以放在任何地方,但是如何讓使用者知道那個是用來啟用拖拽的呢?)。
我假設當使用者點擊enter或者Esc的時候釋放元素,至少我沒找到其他合適的鍵。你想選擇其他的話可以在這裡尋找鍵盤代碼。
case 13: // enter
case 27: // escape
dragDrop.releaseElement();
return false;
事件點擊可以啟用元素。當滑鼠點選連結或者當元素獲得焦點的時候點擊enter鍵就能啟用。所以鍵盤代碼的啟用可以使點擊enter鍵或者點選連結。
(嚴格來說,當你用滑鼠點選連結的時候,元素先被滑鼠事件啟用然後釋放瞭然後再被鍵盤模式啟用。)
事件的其餘部分也非常的模糊。當你想檢測方向鍵的時候鍵盤事件尤為麻煩。
首先我們需要一個允許重複點擊的事件,因為使用者可能按著方向鍵不放,那麼事件就需要一遍遍的觸發,這樣拖拽才能繼續。所以我們使用keypress事件。
不幸的是,IE在keypress的情況下不支援方向鍵。在IE裡面keydown會重複發生,看起來我們需要使用keydown事件了。
你可能才到事情沒那麼簡單。在Opera和Safari裡面keydown事件只能觸發一次,所以當使用者按下鍵之後,元素移動一次之後就不動了。在這些瀏覽器中我們需要keypress。
所以理想情況下,我們使用keypress,如果不支援就是用keydown。但是怎麼切換事件呢?你又怎麼知道keypress在這個時候不能用呢?
我的解決辦法就是給keypress事件設定一個事件處理常式。如果這個程式執行了說明支援keypress,我們就可以安全的切換了。
startDragKeys函數用來設定keydown和keypress事件:
addEventSimple(document,'keydown',dragDrop.dragKeys);addEventSimple(document,'keypress',dragDrop.switchKeyEvents);
首先keydown觸發完成拖拽的dragKeys函數。這是第一個觸發的事件,而且元素總會移動。然後我們做其他的話,那麼元素在Opera和Safari1.3裡面移動一次以後就會停止。
這就是為什麼我們還需要keypress。第一個keypress事件會觸發switchKeyEvents函數,這個函數會調整事件處理常式:
switchKeyEvents: function () {removeEventSimple(document,'keydown',dragDrop.dragKeys);removeEventSimple(document,'keypress',dragDrop.switchKeyEvents);addEventSimple(document,'keypress',dragDrop.dragKeys);},
他會先刪除掉原來的事件處理常式,然後將keypress設定為觸發dragKeys。因為這個函數只會在支援他的瀏覽器裡面執行,所以我們只在這些瀏覽器裡面將keydown改為keypress。
初始鍵盤代碼當使用者點擊了串連啟用了元素,那麼就會調用startDragKeys。
startDragKeys: function () {dragDrop.startDrag(this.relatedElement);dragDrop.dXKeys = dragDrop.dYKeys = 0;addEventSimple(document,'keydown',dragDrop.dragKeys);addEventSimple(document,'keypress',dragDrop.switchKeyEvents);this.blur();return false;},
首先會調用我們之前討論過的startDrag函數。他會給這個函數傳遞relatedElement,也就是要拖拽的元素。
然後將dXKeys和dYKeys設定為0。這些變數用來跟蹤元素的位移。
然後設定事件處理常式,上面已經討論過了。
然後移除剛才點擊的連結的焦點。我這樣做是因為Enter鍵會釋放元素,但是如果不移除焦點,當使用者點擊了Enter鍵之後,元素被釋放,但是連結卻再次被Enter點擊,又成了可拖動的模式。如果我們移除焦點,那麼問題就不存在了。
最後返回false來阻止預設動作。
通過鍵盤拖拽dragKeys負責鍵盤拖拽:
dragKeys: function(e) {var evt = e || window.event;var key = evt.keyCode;
我們首先讀取鍵盤的索引值。
然後我們使用switch語句來決定我們怎麼做。這部分的目的是更新dXKeys和dYKeys的值,就可以通過設定元素的位置來移動元素了。
switch (key) {case 37:// leftcase 63234:dragDrop.dXKeys -= dragDrop.keySpeed;break;case 38:// upcase 63232:dragDrop.dYKeys -= dragDrop.keySpeed;break;case 39:// rightcase 63235:dragDrop.dXKeys += dragDrop.keySpeed;break;case 40:// downcase 63233:dragDrop.dYKeys += dragDrop.keySpeed;break;
作者通過設定keySpeed來確定每次移動的像素大小。當使用者點擊左方向鍵,就減去keySpeed。
這個程式碼封裝含63232-63235的情況。因為Safari1.3沒有使用標準的37-40的方向鍵的索引值(Safari 3已經支援了)。
case 13: // entercase 27: // escapedragDrop.releaseElement();return false;
如果使用者點擊Enter或者Esc鍵,就調用releaseElement()函數。如果你想改變釋放元素的按鍵,可以再這裡添加。
default:return true;}
如果使用者按下了其他鍵,就執行預設動作並且結束函數。
dragDrop.setPosition(dragDrop.dXKeys,dragDrop.dYKeys);
現在dXKeys和dYKeys已經更新我們發送到setPosition()函數中來改變元素的位置。
if (evt.preventDefault)evt.preventDefault();return false;},
最後我們需要阻止預設事件,如果使用者點擊下方向鍵,那麼在執行完上面的代碼後頁面會向下滾動。W3C相容瀏覽器中通過preventDefault實現,在IE中通過返回false實現。
釋放元素當使用者釋放了元素,函數releaseElement就會被調用。他會移除所有代碼設定的事件處理常式,移除dragged類,清理draggedObject然後等待使用者動作。
releaseElement: function() {removeEventSimple(document,'mousemove',dragDrop.dragMouse);removeEventSimple(document,'mouseup',dragDrop.releaseElement);removeEventSimple(document,'keypress',dragDrop.dragKeys);removeEventSimple(document,'keypress',dragDrop.switchKeyEvents);removeEventSimple(document,'keydown',dragDrop.dragKeys);dragDrop.draggedObject.className = dragDrop.draggedObject.className.replace(/dragged/,'');dragDrop.draggedObject = null;}
你或許在使用者釋放元素之後還想做些什麼,可以把你的函數添加在這裡。
翻譯地址:http://www.quirksmode.org/js/dragdrop.html