|
當(dāng)示例的box上的#鏈接處于活動(dòng)狀態(tài)的時(shí)候(不論是用tab然后點(diǎn)擊enter或者使用鼠標(biāo)點(diǎn)擊)這個(gè)元素就能夠通過(guò)方向鍵拖拽。然后點(diǎn)擊enter或者Esc釋放。(可以隨意改變這些鍵。我不確定釋放鍵應(yīng)該設(shè)置成為什么所以enter和Esc都可以)
使用
1、復(fù)制文章后面的dragDrop對(duì)象。
2、復(fù)制我的addEventSimple和removeEventSimple函數(shù),這里需要。
3、設(shè)定keyHTML和keySpeed屬性(下面有解釋)。
4、確定你所要拖拽的元素都有位置屬性:absolute或者fixed。
5、把所有可拖拽的元素發(fā)送到對(duì)象的initElement函數(shù)??梢园l(fā)送一個(gè)對(duì)象或者對(duì)象ID的字符串。例如:
dragDrop.initElement('test');
dragDrop.initElement(document.getElementById('test2'));
6、當(dāng)元素被拖拽過(guò)后,代碼會(huì)自動(dòng)添加dragged類。你可以添加一些CSS效果。
7、如果你想當(dāng)用戶放開元素之后做一些事情,你可以給releaseElement添加自己的函數(shù)。
屬性
你需要設(shè)置兩個(gè)屬性。
keyHTML包含一個(gè)需要拖拽的元素的鍵盤能訪問到的鏈接的內(nèi)容。為了保持HTML簡(jiǎn)潔,這里只添加一個(gè)有簡(jiǎn)單樣式的類。你可以隨意構(gòu)建你的HTML,但是要記住一點(diǎn)就是必須有一個(gè)鏈接讓鍵盤能夠訪問到,鍵盤用戶需要一個(gè)焦點(diǎn)來(lái)觸發(fā)拖拽事件。
keySpeed用來(lái)設(shè)置鍵盤拖拽的速度,每次按鍵移動(dòng)多少像素。我喜歡設(shè)置為10,你也可以嘗試一下其他的值。
這里還有7個(gè)屬性,但是都是在代碼內(nèi)部的。初始化的時(shí)候都設(shè)置為undefined,然后相應(yīng)的函數(shù)會(huì)設(shè)置他們。
拖拽對(duì)象
復(fù)制下面這個(gè)對(duì)象到你的頁(yè)面,不要忘了addEventSimple和removeEventSimple。
復(fù)制代碼 代碼如下:
dragDrop = {
keyHTML: '<a href="#" class="keyLink">#</a>',
keySpeed: 10, // pixels per keypress event
initialMouseX: 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: // left
case 63234:
dragDrop.dXKeys -= dragDrop.keySpeed;
break;
case 38: // up
case 63232:
dragDrop.dYKeys -= dragDrop.keySpeed;
break;
case 39: // right
case 63235:
dragDrop.dXKeys += dragDrop.keySpeed;
break;
case 40: // down
case 63233:
dragDrop.dYKeys += dragDrop.keySpeed;
break;
case 13: // enter
case 27: // escape
dragDrop.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.3
removeEventSimple(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;
}
}
拖拽是什么
拖拽是在屏幕上移動(dòng)元素的一種方法。為了讓元素能夠移動(dòng),元素必須有position屬性:absolute或者fixed,這樣才能通過(guò)修改它的坐標(biāo)(style.top和style.left)讓它移動(dòng)。
(理論上position:relative也可以,但是幾乎沒用。另外,那樣需要額外的數(shù)據(jù)來(lái)計(jì)算,這里我沒有寫)
設(shè)置坐標(biāo)很簡(jiǎn)單;找到需要設(shè)置的元素的坐標(biāo)是這個(gè)代碼比較難的部分。大多數(shù)代碼都是用來(lái)處理這個(gè)問題的。
另外,保持易用性也比較重要。傳統(tǒng)上通過(guò)鼠標(biāo)來(lái)拖拽一個(gè)元素是最好的辦法,但是也要考慮到?jīng)]有鼠標(biāo)的用戶,所以也要保證鍵盤的可用性。
基礎(chǔ)知識(shí)
讓我們先來(lái)看看一些基礎(chǔ)知識(shí)
初始化一個(gè)元素
每個(gè)拖拽代碼都從初始化元素開始。這個(gè)工作通過(guò)下面的函完成:
復(fù)制代碼 代碼如下:
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;
},
如果函數(shù)接收到一個(gè)字符串,那么就會(huì)當(dāng)做元素ID來(lái)處理。然后給這個(gè)元素設(shè)置一個(gè)onmousedown事件,用來(lái)開始鼠標(biāo)部分的代碼。注意這里我使用的是傳統(tǒng)事件注冊(cè)方式;因?yàn)槲蚁M鹴his關(guān)鍵字能夠在startDragDrop里起作用。
然后把用戶定義的keyHTML添加到元素上,我相信這個(gè)鏈接是用來(lái)觸發(fā)鍵盤事件的。然后為這個(gè)鏈接設(shè)置鍵盤的觸發(fā)程序。然后存儲(chǔ)主元素在relatedElement里面,我們后面需要。
現(xiàn)在代碼就等用戶動(dòng)作了
基本位置信息
我打算使用下面的方法來(lái):首先我會(huì)讀取拖拽元素的初始位置,保存在startX和startY里面。然后計(jì)算鼠標(biāo)移動(dòng)的位置或者鍵盤控制下移動(dòng)的位置來(lái)決定元素從初始位置移動(dòng)的范圍。
startX和startY通過(guò)startDrag函數(shù)來(lái)設(shè)置,這個(gè)函數(shù)在鼠標(biāo)和鍵盤事件里都會(huì)用到。
復(fù)制代碼 代碼如下:
startDrag: function (obj) {
if (dragDrop.draggedObject)
dragDrop.releaseElement();
dragDrop.startX = obj.offsetLeft;
dragDrop.startY = obj.offsetTop;
dragDrop.draggedObject = obj;
obj.className += ' dragged';
},
首先,如果元素處于拖拽狀態(tài),那么我們就釋放他(我們后面再講)。
然后函數(shù)會(huì)找到元素在起始位置的坐標(biāo)(offsetLeft和offsetTop),然后保存在startX和startY以備后用。
然后在draggedObject里面保存一個(gè)對(duì)象的引用。然后給他添加dragged類,這樣就可以通過(guò)CSS來(lái)設(shè)置拖拽時(shí)候的樣式了
當(dāng)用戶使用鼠標(biāo)或者鍵盤拖拽元素的時(shí)候,代碼的最復(fù)雜的部分就要跟蹤位置的變化。然后給出dX和dY(X和Y的變化)。然后加上startX和startY就是元素現(xiàn)在的位置。
下面的函數(shù)用來(lái)設(shè)定位置:
復(fù)制代碼 代碼如下:
setPosition: function (dx,dy) {
dragDrop.draggedObject.style.left = dragDrop.startX + dx + 'px';
dragDrop.draggedObject.style.top = dragDrop.startY + dy + 'px';
},
函數(shù)通過(guò)鼠標(biāo)和鍵盤的移動(dòng)計(jì)算所得的dX和dY與初始位置,來(lái)設(shè)定元素的新位置。
這部分很簡(jiǎn)單,復(fù)雜的地方就在于dx和dy的獲得,鼠標(biāo)部分和鍵盤部分的處理非常的不同,我們分別來(lái)看。
鼠標(biāo)部分的代碼
鼠標(biāo)部分的計(jì)算方面比鍵盤的要復(fù)雜一些,但是在瀏覽器兼容性方面問題不大。所以我們從鼠標(biāo)部分開始。
事件
首先我們來(lái)討論事件。很明顯的,在拖拽過(guò)程中需要mousedown,mousemove,mouseup用來(lái)完成選擇對(duì)象,拖拽,拖拽完成這幾個(gè)動(dòng)作。
這一系列事件從需要拖拽元素的mousedown事件開始。所以所有的拖拽元素都需要這個(gè)事件來(lái)標(biāo)明拖拽開始了。我們看到:
復(fù)制代碼 代碼如下:element.onmousedown = dragDrop.startDragMouse;
然而mousemove和mouseup事件不應(yīng)該設(shè)置在元素上而應(yīng)該設(shè)置在整個(gè)文檔上。因?yàn)橛脩艨赡芎芸旌墀偪竦?a href=/yuedu/yidong/ target=_blank class=infotextkey>移動(dòng)鼠標(biāo),然后丟失拖拽元素。如果設(shè)置在元素上,因?yàn)槭髽?biāo)不在元素上了所以可能會(huì)無(wú)法控制,這對(duì)于易用性來(lái)說(shuō)不是好事。
如果我們?cè)傥臋n上設(shè)置mousemove和mouseup,就沒有這個(gè)問題。不管鼠標(biāo)在哪,元素都會(huì)響應(yīng)mousemove和mouseup。這個(gè)的易用性就很強(qiáng)。
另外你只能在拖拽開始以后再設(shè)置mousemove和mouseup,然后當(dāng)用戶釋放元素之后刪除它們。這樣代碼很干凈而且節(jié)省系統(tǒng)資源,因?yàn)閙ousemove對(duì)系統(tǒng)的消耗很大。
Mousedown
當(dāng)拖拽元素發(fā)生mousedown事件的時(shí)候,startDragMouse函數(shù)就開始執(zhí)行:
首先會(huì)執(zhí)行我們之前討論過(guò)的startDrag。然后查找鼠標(biāo)的坐標(biāo)然后保存在initialMouseX和initialMouseY中。后面我們會(huì)把鼠標(biāo)位置跟這個(gè)比較。
最后會(huì)返回false,這個(gè)用來(lái)阻止默認(rèn)鼠標(biāo)事件:選擇文本。我們不想再拖拽的時(shí)候有文本被選中,這很煩人。
然后給文檔設(shè)置mouseup和mousemove事件處理程序。因?yàn)橛锌赡芪臋n有他自己的mouseup和mousemove事件處理程序,所以我使用我的addEventSimple函數(shù)防止原來(lái)的事件處理程序失效。
Mousemove
現(xiàn)在,當(dāng)用戶移動(dòng)鼠標(biāo)的時(shí)候dragMouse函數(shù)就執(zhí)行了。
復(fù)制代碼 代碼如下:
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;
},
這個(gè)函數(shù)會(huì)讀取鼠標(biāo)現(xiàn)在的坐標(biāo),然后減去之前的坐標(biāo),把得到的dX和dY傳遞給sePosition。
然后通過(guò)返回false來(lái)阻止鼠標(biāo)選擇文本的默認(rèn)屬性。
Mouseup
當(dāng)用戶松開鼠標(biāo)的時(shí)候,會(huì)調(diào)用releaseElement。我們后面討論。
鍵盤部分代碼
現(xiàn)在我們開始更復(fù)雜的鍵盤部分代碼。不像鼠標(biāo)拖拽那樣,鍵盤拖拽并沒有一個(gè)標(biāo)準(zhǔn)。雖說(shuō)基本的交互不是太復(fù)雜,但是最好還是簡(jiǎn)要說(shuō)明一下。
基本交互
用來(lái)拖拽的鍵最好是方向鍵,這很簡(jiǎn)單。
激活和釋放元素是比較有技巧的,在這里我的代碼還需要加強(qiáng)。
我覺得如果用鍵盤來(lái)激活的話就應(yīng)該使用一個(gè)我添加的額外的鏈接。這里沒有太多選擇:因?yàn)殒溄幽軌蛟谒械臑g覽器里面獲得焦點(diǎn)(好吧,表單也可以,你也可是選擇復(fù)選框),而且把一個(gè)鏈接放置在可拖拽的元素里面也是合乎邏輯的(你可以放在任何地方,但是如何讓用戶知道那個(gè)是用來(lái)激活拖拽的呢?)。
我假設(shè)當(dāng)用戶點(diǎn)擊enter或者Esc的時(shí)候釋放元素,至少我沒找到其他合適的鍵。你想選擇其他的話可以在這里查找鍵盤代碼。
case 13: // enter
case 27: // escape
dragDrop.releaseElement();
return false;
事件
點(diǎn)擊可以激活元素。當(dāng)鼠標(biāo)點(diǎn)擊鏈接或者當(dāng)元素獲得焦點(diǎn)的時(shí)候點(diǎn)擊enter鍵就能激活。所以鍵盤代碼的激活可以使點(diǎn)擊enter鍵或者點(diǎn)擊鏈接。
(嚴(yán)格來(lái)說(shuō),當(dāng)你用鼠標(biāo)點(diǎn)擊鏈接的時(shí)候,元素先被鼠標(biāo)事件激活然后釋放了然后再被鍵盤模式激活。)
事件的其余部分也非常的模糊。當(dāng)你想檢測(cè)方向鍵的時(shí)候鍵盤事件尤為麻煩。
首先我們需要一個(gè)允許重復(fù)點(diǎn)擊的事件,因?yàn)橛脩艨赡馨粗较蜴I不放,那么事件就需要一遍遍的觸發(fā),這樣拖拽才能繼續(xù)。所以我們使用keypress事件。
不幸的是,IE在keypress的情況下不支持方向鍵。在IE里面keydown會(huì)重復(fù)發(fā)生,看起來(lái)我們需要使用keydown事件了。
你可能才到事情沒那么簡(jiǎn)單。在Opera和Safari里面keydown事件只能觸發(fā)一次,所以當(dāng)用戶按下鍵之后,元素移動(dòng)一次之后就不動(dòng)了。在這些瀏覽器中我們需要keypress。
所以理想情況下,我們使用keypress,如果不支持就是用keydown。但是怎么切換事件呢?你又怎么知道keypress在這個(gè)時(shí)候不能用呢?
我的解決辦法就是給keypress事件設(shè)置一個(gè)事件處理程序。如果這個(gè)程序執(zhí)行了說(shuō)明支持keypress,我們就可以安全的切換了。
startDragKeys函數(shù)用來(lái)設(shè)置keydown和keypress事件:
復(fù)制代碼 代碼如下:
addEventSimple(document,'keydown',dragDrop.dragKeys);
addEventSimple(document,'keypress',dragDrop.switchKeyEvents);
首先keydown觸發(fā)完成拖拽的dragKeys函數(shù)。這是第一個(gè)觸發(fā)的事件,而且元素總會(huì)移動(dòng)。然后我們做其他的話,那么元素在Opera和Safari1.3里面移動(dòng)一次以后就會(huì)停止。
這就是為什么我們還需要keypress。第一個(gè)keypress事件會(huì)觸發(fā)switchKeyEvents函數(shù),這個(gè)函數(shù)會(huì)調(diào)整事件處理程序:
復(fù)制代碼 代碼如下:
switchKeyEvents: function () {
removeEventSimple(document,'keydown',dragDrop.dragKeys);
removeEventSimple(document,'keypress',dragDrop.switchKeyEvents);
addEventSimple(document,'keypress',dragDrop.dragKeys);
},
他會(huì)先刪除掉原來(lái)的事件處理程序,然后將keypress設(shè)置為觸發(fā)dragKeys。因?yàn)檫@個(gè)函數(shù)只會(huì)在支持他的瀏覽器里面執(zhí)行,所以我們只在這些瀏覽器里面將keydown改為keypress。
初始鍵盤代碼
當(dāng)用戶點(diǎn)擊了連接激活了元素,那么就會(huì)調(diào)用startDragKeys。
復(fù)制代碼 代碼如下:
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;
},
首先會(huì)調(diào)用我們之前討論過(guò)的startDrag函數(shù)。他會(huì)給這個(gè)函數(shù)傳遞relatedElement,也就是要拖拽的元素。
然后將dXKeys和dYKeys設(shè)置為0。這些變量用來(lái)跟蹤元素的位移。
然后設(shè)置事件處理程序,上面已經(jīng)討論過(guò)了。
然后移除剛才點(diǎn)擊的鏈接的焦點(diǎn)。我這樣做是因?yàn)镋nter鍵會(huì)釋放元素,但是如果不移除焦點(diǎn),當(dāng)用戶點(diǎn)擊了Enter鍵之后,元素被釋放,但是鏈接卻再次被Enter點(diǎn)擊,又成了可拖動(dòng)的模式。如果我們移除焦點(diǎn),那么問題就不存在了。
最后返回false來(lái)阻止默認(rèn)動(dòng)作。
通過(guò)鍵盤拖拽
dragKeys負(fù)責(zé)鍵盤拖拽:
復(fù)制代碼 代碼如下:
dragKeys: function(e) {
var evt = e || window.event;
var key = evt.keyCode;
我們首先讀取鍵盤的鍵值。
然后我們使用switch語(yǔ)句來(lái)決定我們?cè)趺醋觥_@部分的目的是更新dXKeys和dYKeys的值,就可以通過(guò)設(shè)置元素的位置來(lái)移動(dòng)元素了。
復(fù)制代碼 代碼如下:
switch (key) {
case 37: // left
case 63234:
dragDrop.dXKeys -= dragDrop.keySpeed;
break;
case 38: // up
case 63232:
dragDrop.dYKeys -= dragDrop.keySpeed;
break;
case 39: // right
case 63235:
dragDrop.dXKeys += dragDrop.keySpeed;
break;
case 40: // down
case 63233:
dragDrop.dYKeys += dragDrop.keySpeed;
break;
作者通過(guò)設(shè)置keySpeed來(lái)確定每次移動(dòng)的像素大小。當(dāng)用戶點(diǎn)擊左方向鍵,就減去keySpeed。
這個(gè)代碼包含63232-63235的情況。因?yàn)镾afari1.3沒有使用標(biāo)準(zhǔn)的37-40的方向鍵的鍵值(Safari 3已經(jīng)支持了)。
復(fù)制代碼 代碼如下:case 13: // enter
case 27: // escape
dragDrop.releaseElement();
return false;
如果用戶點(diǎn)擊Enter或者Esc鍵,就調(diào)用releaseElement()函數(shù)。如果你想改變釋放元素的按鍵,可以再這里添加。
復(fù)制代碼 代碼如下:default:
return true;
}
如果用戶按下了其他鍵,就執(zhí)行默認(rèn)動(dòng)作并且結(jié)束函數(shù)。
復(fù)制代碼 代碼如下: dragDrop.setPosition(dragDrop.dXKeys,dragDrop.dYKeys);
現(xiàn)在dXKeys和dYKeys已經(jīng)更新我們發(fā)送到setPosition()函數(shù)中來(lái)改變?cè)氐奈恢谩?
復(fù)制代碼 代碼如下:if (evt.preventDefault)
evt.preventDefault();
return false;
},
最后我們需要阻止默認(rèn)事件,如果用戶點(diǎn)擊下方向鍵,那么在執(zhí)行完上面的代碼后頁(yè)面會(huì)向下滾動(dòng)。W3C兼容瀏覽器中通過(guò)preventDefault實(shí)現(xiàn),在IE中通過(guò)返回false實(shí)現(xiàn)。
釋放元素
當(dāng)用戶釋放了元素,函數(shù)releaseElement就會(huì)被調(diào)用。他會(huì)移除所有代碼設(shè)置的事件處理程序,移除dragged類,清理draggedObject然后等待用戶動(dòng)作。
復(fù)制代碼 代碼如下:
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;
}
你或許在用戶釋放元素之后還想做些什么,可以把你的函數(shù)添加在這里。
翻譯地址:http://www.quirksmode.org/js/dragdrop.html
轉(zhuǎn)載請(qǐng)保留以下信息
作者:北玉(tw:@rehawk)
JavaScript技術(shù):JavaScript CSS修改學(xué)習(xí)第六章 拖拽,轉(zhuǎn)載需保留來(lái)源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。