javascript 拖放效果實(shí)現(xiàn)代碼
更新時(shí)間:2010年01月22日 09:34:33 作者:
JavaScript擅長于修改頁面中的DOM元素,但是我們使用JavaScript通常只是實(shí)現(xiàn)一些簡單功能,例如實(shí)現(xiàn)圖片的翻轉(zhuǎn),網(wǎng)頁中的標(biāo)簽頁,等等。這篇文章將向你展示如何在頁面中,對(duì)創(chuàng)建的元素實(shí)現(xiàn)拖放。
有許多理由讓你在頁面中加入拖放的功能,其中最簡單的理由是重新組織數(shù)據(jù)。舉個(gè)例子,你可能希望用戶能夠重組一系列的頁面元素,通過放置一個(gè)input或 select組件在各個(gè)元素的旁邊來代表它們的順序是一種解決方案,使該組元素可以被拖放是一種替代方案。或者也許你想在網(wǎng)站上擁有一個(gè)可以被用戶移動(dòng)的導(dǎo)航窗口。這些都是使用拖放功能的簡單理由,因?yàn)槟隳軌驅(qū)崿F(xiàn)!
在你的網(wǎng)頁上實(shí)現(xiàn)拖放的效果并不是很復(fù)雜。首先,我們知道鼠標(biāo)的位置,然后我們需要了解用戶什么時(shí)候點(diǎn)擊一個(gè)元素,以至于我們知道要準(zhǔn)備開始拖動(dòng)它,最后我們要移動(dòng)這個(gè)元素。
捕獲鼠標(biāo)的移動(dòng)
第一步,我們需要獲取鼠標(biāo)的坐標(biāo),通過一個(gè)函數(shù)并賦給document.onmousemove可以實(shí)現(xiàn)這一功能:
代碼
document.onmousemove = mouseMove;
function mouseMove(ev) {
ev = ev || window.event;
var mousePos = mouseCoords(ev);
}
function mouseCoords(ev) {
if(ev.pageX || ev.pageY) {
return {x:ev.pageX, y:ev.pageY};
}
return {
x:ev.clientX + document.body.scrollLeft - document.body.clientLeft,
y:ev.clientY + document.body.scrollTop - document.body.clientTop
};
}
首先我們需要解釋一下event對(duì)象。不論你什么時(shí)候移動(dòng)、點(diǎn)擊鼠標(biāo),或按鍵,等等,一個(gè)事件都會(huì)發(fā)生。在IE中,這個(gè)事件是全局的,它被存儲(chǔ)在 window.event中,對(duì)于Firefox,及其他的瀏覽器來說,這個(gè)事件將被傳遞到任何指向這個(gè)頁面動(dòng)作的函數(shù)中。因此,我們使 document.onmousemove指向鼠標(biāo)移動(dòng)的函數(shù),鼠標(biāo)移動(dòng)的函數(shù)獲得事件對(duì)象。
上述代碼中,ev在所有瀏覽器環(huán)境中都包含了event對(duì)象。在Firefox里,"||window.event"將被忽略,因?yàn)樗呀?jīng)包含事件。在IE中,ev的值為空,以至于需要將它的值設(shè)置為window.event。
本文中我們需要多次捕獲到鼠標(biāo)的坐標(biāo),因此我們寫了一個(gè)mouseCoords方法,它有一個(gè)參數(shù):event。
我們要再次討論IE和其他瀏覽器之間的差異。Firefox和其他的瀏覽器使用event.pageX和event.pageY來表示鼠標(biāo)相對(duì)于 document文檔的位置。如果你有一個(gè)500*500的窗口,并且鼠標(biāo)位于窗口中間,那么pageX和pageY的值將都是250。如果你將窗口向下滾動(dòng)500象素,pageY的值為750。
如此相反的是,微軟的IE使用event.clientX和event.clientY來表示鼠標(biāo)相對(duì)于window窗口的位置,而不是當(dāng)前 document文檔。在相同的例子中,如果將鼠標(biāo)放置于500*500窗口的中間,clientX和clientY值將均為250。如果向下滾動(dòng)頁面, clientY將仍為250,因?yàn)樗窍鄬?duì)于window窗口來測(cè)量,而不是當(dāng)前的document文檔。因此,在鼠標(biāo)位置中,我們應(yīng)該引入 document文檔body區(qū)域的scrollLeft和scrollTop屬性。最后,IE中document文檔實(shí)際并不在(0,0)的位置,在它周圍有一個(gè)小(通常有2px)邊框,document.body.clientLeft和document.body.clientTop包含了這個(gè)邊框的寬度,從而還需要在鼠標(biāo)位置中引入它們。
幸運(yùn)的是,現(xiàn)在我們擁有了mouseCoords函數(shù),不用再為獲取鼠標(biāo)位置擔(dān)心了。
捕獲鼠標(biāo)的點(diǎn)擊
下一步,我們必須知道鼠標(biāo)何時(shí)點(diǎn)擊及何時(shí)釋放。如果我們跳過這一步,只要你的鼠標(biāo)移動(dòng)經(jīng)過這些元素時(shí),都將產(chǎn)生拖動(dòng)這些元素的效果,這是令人討厭并違反人的直覺的。
在這里,有兩個(gè)函數(shù)可以幫助我們:onmousedown和onmouseup。先前我們已將document.onmousemove指向一個(gè)函數(shù),因此從邏輯上似乎應(yīng)該使document.onmousedown和document.onmouseup都指向函數(shù)。如果我們讓 document.onmousedown指向一個(gè)函數(shù),那么這個(gè)函數(shù)將會(huì)因?yàn)槭髽?biāo)點(diǎn)擊任何元素而執(zhí)行:文本、圖像、表格,等等。我們只想頁面中特定的元素具有被拖放的功能,因此,我們可以通過如下方法實(shí)現(xiàn):
代碼
document.onmouseup = mouseUp;
var dragObject = null;
function makeClickable(object) {
object.onmousedown = function() {
dragObject = this;
}
}
function mouseUp(ev) {
dragObject = null;
}
我們現(xiàn)在有了一個(gè)變量dragObject,包含了你點(diǎn)擊的任何元素。當(dāng)你釋放鼠標(biāo)的時(shí)候,dragObject被設(shè)置為空,從而在dragObject非空的時(shí)候,我們需要進(jìn)行拖動(dòng)操作。
移動(dòng)元素
我們現(xiàn)在已經(jīng)知道如何捕獲鼠標(biāo)移動(dòng)和點(diǎn)擊。接下來需要做的就是移動(dòng)任何我們想拖動(dòng)的元素。首先,將一個(gè)元素準(zhǔn)確移動(dòng)到頁面上我們想要的位置,該元素樣式表的position值必須為absolute,這意味著你可以設(shè)置它的style.top或style.left,測(cè)量值相對(duì)于頁面的左上角,因?yàn)槲覀兯械氖髽?biāo)移動(dòng)都是相對(duì)于頁面左上角的,通常都是這樣。
一旦我們?cè)O(shè)置了item.style.position='absolute',接下來就需要改變?cè)撛豻op和left的位置,使它移動(dòng)!
代碼
document.onmousemove = mouseMove;
document.onmouseup = mouseUp;
var dragObject = null ;
var mouseOffset = null ;
function getMouseOffset(target, ev) {
ev = ev || window.event;
var docPos = getPosition(target);
var mousePos = mouseCoords(ev);
return {x:mousePos.x - docPos.x, y:mousePos.y - docPos.y} ;
}
function getPosition(e) {
var left = 0;
var top = 0;
while (e.offsetParent) {
left += e.offsetLeft;
top += e.offsetTop;
e = e.offsetParent;
}
left += e.offsetLeft;
top += e.offsetTop;
return {x:left, y:top} ;
}
function mouseMove(ev) {
ev = ev || window.event;
var mousePos = mouseCoords(ev);
if (dragObject) {
dragObject.style.position = 'absolute';
dragObject.style.top = mousePos.y - mouseOffset.y;
dragObject.style.left = mousePos.x - mouseOffset.x;
return false ;
}
}
function mouseUp() {
dragObject = null ;
}
function makeDraggable(item) {
if (!item) return ;
item.onmousedown = function (ev) {
dragObject = this ;
mouseOffset = getMouseOffset(this, ev);
return false ;
}
}
你會(huì)注意到這些代碼是以我們前面的例子為基礎(chǔ)的(參考上篇文章),將它們放置在一起,你將能夠隨意的去移動(dòng)元素。
當(dāng)我們點(diǎn)擊一個(gè)元素時(shí),存儲(chǔ)了另外的一個(gè)變量,mouseOffset。mouseOffset簡單的包含了我們點(diǎn)擊元素的位置信息。如果我們有一張 20*20px的圖像,然后點(diǎn)擊圖像的中間,mouseOffset應(yīng)該是{x:10, y:10}。如果我們點(diǎn)擊圖像的左上角,mouseOffset應(yīng)為{x:0, y:0}。我們?cè)谑髽?biāo)移動(dòng)后的位置信息中用到它。如果我們沒有存儲(chǔ)這個(gè)值,不論你點(diǎn)擊元素的哪一個(gè)位置,元素相對(duì)于鼠標(biāo)的位置都將會(huì)是相同的。
mouseOffset函數(shù)用到了另外一個(gè)函數(shù)getPosition。getPosition目的是返回元素相對(duì)于documemt文檔的坐標(biāo)位置。如果我們簡單的去讀取item.offsetLeft或item.style.left,得到的將是元素相對(duì)于它父元素的位置,而不是document文檔的。在我們的腳本中,所有的元素都是相對(duì)于document文檔的,因此需要這樣做。
要完成獲取元素相對(duì)于document文檔位置的工作,getPosition從它自身的父級(jí)開始,循環(huán)獲取它的left和top的值并累加,這樣我們就得到了我們想要的元素距文檔頂部和左側(cè)的累計(jì)值。
當(dāng)我們獲取了這條信息并移動(dòng)鼠標(biāo)的時(shí)候,mouseMove開始運(yùn)行。首先我們需要保證item.style.position值為absolute,接著,我們將元素移動(dòng)到任何一個(gè)地方,鼠標(biāo)位置都會(huì)減去我們之前記錄的鼠標(biāo)相對(duì)于元素的偏移量。當(dāng)鼠標(biāo)釋放時(shí),dragObject將被設(shè)置為null,并且mouseMove函數(shù)不再做任何事情。
放置元素
我們前面的例子已經(jīng)處理了這個(gè)問題,僅僅是拖動(dòng)一個(gè)元素,然后將它放下。然后,在我們放下元素的時(shí)候通常還有其他的目的,我們以拖動(dòng)元素到垃圾回收站為例,或我們可能想讓該元素和頁面中某個(gè)特定的區(qū)域?qū)R。
不幸的是我們?cè)谶@里進(jìn)入了一個(gè)相對(duì)主要的問題。因?yàn)槲覀冋谝苿?dòng)的元素總是直接處于我們的鼠標(biāo)下,而不可能去引發(fā)mouseover、 mousedown、mouseup或鼠標(biāo)對(duì)頁面中其他元素的操作。如果你移動(dòng)一個(gè)元素到垃圾回收站,你的鼠標(biāo)會(huì)一直在移動(dòng)元素的上方,而不是垃圾回收站。
那么我們?cè)撊绾翁幚磉@個(gè)問題呢?這里有幾種解決方案。在前面所提到的mouseOffset的目的是保證元素總是在鼠標(biāo)下方正確的位置,如果你忽視了這點(diǎn),然后總是使得元素在鼠標(biāo)的右下方,你的鼠標(biāo)將不會(huì)被你正在拖動(dòng)的元素所隱藏,我們也不會(huì)碰到問題。但事實(shí)上往往不會(huì)這樣,為了美觀我們通常要保持元素在鼠標(biāo)的下方。
另外一種選擇是不移動(dòng)你正在拖動(dòng)的元素,你可以改變鼠標(biāo)樣式,來告訴使用者你正在拖動(dòng)一個(gè)元素,直到你將它放置到某個(gè)地方。這解決了我們的問題,但是帶來了和前面一種方案面臨的同樣問題:美觀。
我們最后的一種解決方案既不影響你正在移動(dòng)的元素,也不影響移動(dòng)終點(diǎn)位置上的元素(例如垃圾回收站)。不幸的是,這比前面兩種解決方案的難度更大。我們將要做的是獲得一組我們要放置的目標(biāo),當(dāng)鼠標(biāo)釋放時(shí),我們手工檢查當(dāng)前鼠標(biāo)相對(duì)于每個(gè)目標(biāo)的位置,看鼠標(biāo)是否釋放在這個(gè)目標(biāo)中某一個(gè)目標(biāo)的位置上,如果是的,我們就知道我們已經(jīng)將元素放置在我們的目標(biāo)上了。
代碼
/*
All code from the previous example is needed with the exception
of the mouseUp function which is replaced below
*/
var dropTargets = [];
function addDropTarget(dropTarget) {
dropTargets.push(dropTarget);
}
function mouseUp(ev) {
ev = ev || window.event;
var mousePos = mouseCoords(ev);
for (var i = 0; i < dropTargets.length; i ++) {
var curTarget = dropTargets[i];
var targPos = getPosition(curTarget);
var targWidth = parseInt(curTarget.offsetWidth);
var targHeight = parseInt(curTarget.offsetHeight);
if (
(mousePos.x > targPos.x) &&
(mousePos. < (targPos.x + targWidth)) &&
(mousePos.y > targPos.y) &&
(mousePos.y < (targPos.y + targHeight))) {
// dragObject was dropped onto curTarget!
}
}
dragObject = null ;
}
這個(gè)例子中當(dāng)鼠標(biāo)釋放時(shí),我們循環(huán)每個(gè)可能放置元素的目標(biāo),如果鼠標(biāo)指針在目標(biāo)上,我們則擁有了一個(gè)放置元素的事件,通過鼠標(biāo)橫坐標(biāo)大于目標(biāo)元素左側(cè)橫坐標(biāo)(mousePos.x>targPos.x),小于目標(biāo)元素右側(cè)橫坐標(biāo)(mousePos.x<(targPos.x+ targWidth))來判定,對(duì)于Y坐標(biāo)我們做同樣的判斷。如果所有的這些值都返回true,那么我們的鼠標(biāo)就是在目標(biāo)元素的范圍內(nèi)。
整合所有的功能
最后我們使用所有代碼片斷,來創(chuàng)建一個(gè)完整的拖放函數(shù)腳本。我們所要做的第一件事情是DOM操作,如果你對(duì)此并不十分熟悉,可以閱讀《JavaScript Primer on DOM Manipulation》。
接下來的代碼創(chuàng)建容器和容器組,使得在這些容器中可以拖動(dòng)每個(gè)元素,這在本文第二個(gè)demo的基礎(chǔ)上來完成。這段代碼能夠用來重新規(guī)劃元素的順序,將導(dǎo)航窗口放在頁面的左側(cè)或右側(cè),或再加入你所能想到的其他的功能。
我們將使用偽代碼來一步步進(jìn)行講解,將真實(shí)的代碼通過注釋的方式留給讀者查看。
1.當(dāng)文檔第一次被加載時(shí),我們創(chuàng)建一個(gè)名為dragHelper的DIV標(biāo)簽,當(dāng)我們開始移動(dòng)一個(gè)元素的時(shí)候,dragHelper將成為一個(gè)隱藏元素,可以四處移動(dòng)。真實(shí)的元素并不會(huì)被拖動(dòng),僅僅使用insertBefore和appendChild來移動(dòng)。我們?cè)陂_始的時(shí)候隱藏dragHelper。
2.我們創(chuàng)建mouseDown和mouseUp函數(shù)。起初,所有的這些函數(shù)都假設(shè)記錄了鼠標(biāo)按鈕的狀態(tài),以至于iMouseDown變量在鼠標(biāo)按下的時(shí)候?yàn)閠rue,沒有按下的時(shí)候?yàn)閒alse。
3.我們創(chuàng)建一個(gè)全局變量DragDrops,以及一個(gè)函數(shù)CreateDragContainer。DragDrops包含一組相互關(guān)聯(lián)的容器。傳入CreateDragContainer的任何變量(代表容器)被組織成一個(gè)新的集合,使元素能夠在這些容器間自由移動(dòng)。通過setAttribute,CreateDragContainer函數(shù)同樣將各容器中的元素綁定在一起。
4.現(xiàn)在我們的代碼知道每個(gè)元素所在的集合,現(xiàn)在來看mouseMove函數(shù)。mouseMove函數(shù)首先設(shè)置了一個(gè)變量target,表示鼠標(biāo)下面的目標(biāo)元素,如果這個(gè)元素在集合(用getAttribute判斷)中就繼續(xù)下面操作:
4.1.首先,在必要的時(shí)候,我們運(yùn)行一個(gè)簡單的腳本來改變目標(biāo)元素的class屬性,這樣就創(chuàng)造了一個(gè)翻動(dòng)的效果。
4.2.然后我們檢查鼠標(biāo)是否點(diǎn)擊(因?yàn)槲覀兊拇a已經(jīng)運(yùn)行到這里),如果事件發(fā)生:
4.2.1.設(shè)置變量curTarget為當(dāng)前元素。
4.2.2.記錄元素當(dāng)前在文檔中的位置,以便在需要的時(shí)候可以將它的值取回。
4.2.3.將當(dāng)前元素克隆到dragHelper,使得我們能夠移動(dòng)元素的隱藏備份。
4.2.4.因?yàn)樵赿ragHelper中我們完全擁有了拖動(dòng)元素的一個(gè)備份,這個(gè)元素會(huì)始終在鼠標(biāo)下,我們必須移除dragObj屬性,讓代碼知道dragObj已不在集合中。
4.2.5.我們快速記錄集合中每個(gè)元素當(dāng)前的位置、寬度和高度。當(dāng)元素第一次開始被拖動(dòng)時(shí),我們僅需做一次這種工作,否則每當(dāng)鼠標(biāo)移動(dòng)的時(shí)候我們都必須做一次,甚至一秒內(nèi)幾百次。
4.3.如果鼠標(biāo)沒有點(diǎn)擊,要么我們和之前擁有同樣的目標(biāo)元素,要么沒有目標(biāo)元素,不論哪種情況我們都不會(huì)做任何事情。
5.現(xiàn)在我們檢查curTarget變量。curTarget應(yīng)該僅包含一個(gè)被拖動(dòng)的對(duì)象,因此如果它存在,表示我們正在拖動(dòng)一個(gè)元素:
5.1.移動(dòng)隱藏DIV到鼠標(biāo),這個(gè)元素和文章前面所創(chuàng)建的元素一樣能夠被拖動(dòng)。
5.2.然后我們檢查鼠標(biāo)是否存在于當(dāng)前集合中每個(gè)容器中。
5.2.1.如果鼠標(biāo)在某個(gè)容器中,我們檢查容器中的每個(gè)元素,查看我們正拖動(dòng)的元素屬于哪個(gè)位置。
5.2.2.然后我們將所拖動(dòng)的元素放置在容器中另一個(gè)元素的前面,或容器的最后位置。
5.2.3.最后我們確定元素可見。
6.剩下的事情就是捕獲mouseUp事件:
6.1.首先需要隱藏dragHelper:它不再被需要,因?yàn)槲覀儧]有拖動(dòng)任何東西。
6.2.如果拖動(dòng)的元素是可見的,它已經(jīng)存在于任何它所屬的容器中,所有工作已完成。
6.3.如果拖動(dòng)的元素不可見,我們將它放回它原來所在的地方。
在你的網(wǎng)頁上實(shí)現(xiàn)拖放的效果并不是很復(fù)雜。首先,我們知道鼠標(biāo)的位置,然后我們需要了解用戶什么時(shí)候點(diǎn)擊一個(gè)元素,以至于我們知道要準(zhǔn)備開始拖動(dòng)它,最后我們要移動(dòng)這個(gè)元素。
捕獲鼠標(biāo)的移動(dòng)
第一步,我們需要獲取鼠標(biāo)的坐標(biāo),通過一個(gè)函數(shù)并賦給document.onmousemove可以實(shí)現(xiàn)這一功能:
代碼
復(fù)制代碼 代碼如下:
document.onmousemove = mouseMove;
function mouseMove(ev) {
ev = ev || window.event;
var mousePos = mouseCoords(ev);
}
function mouseCoords(ev) {
if(ev.pageX || ev.pageY) {
return {x:ev.pageX, y:ev.pageY};
}
return {
x:ev.clientX + document.body.scrollLeft - document.body.clientLeft,
y:ev.clientY + document.body.scrollTop - document.body.clientTop
};
}
首先我們需要解釋一下event對(duì)象。不論你什么時(shí)候移動(dòng)、點(diǎn)擊鼠標(biāo),或按鍵,等等,一個(gè)事件都會(huì)發(fā)生。在IE中,這個(gè)事件是全局的,它被存儲(chǔ)在 window.event中,對(duì)于Firefox,及其他的瀏覽器來說,這個(gè)事件將被傳遞到任何指向這個(gè)頁面動(dòng)作的函數(shù)中。因此,我們使 document.onmousemove指向鼠標(biāo)移動(dòng)的函數(shù),鼠標(biāo)移動(dòng)的函數(shù)獲得事件對(duì)象。
上述代碼中,ev在所有瀏覽器環(huán)境中都包含了event對(duì)象。在Firefox里,"||window.event"將被忽略,因?yàn)樗呀?jīng)包含事件。在IE中,ev的值為空,以至于需要將它的值設(shè)置為window.event。
本文中我們需要多次捕獲到鼠標(biāo)的坐標(biāo),因此我們寫了一個(gè)mouseCoords方法,它有一個(gè)參數(shù):event。
我們要再次討論IE和其他瀏覽器之間的差異。Firefox和其他的瀏覽器使用event.pageX和event.pageY來表示鼠標(biāo)相對(duì)于 document文檔的位置。如果你有一個(gè)500*500的窗口,并且鼠標(biāo)位于窗口中間,那么pageX和pageY的值將都是250。如果你將窗口向下滾動(dòng)500象素,pageY的值為750。
如此相反的是,微軟的IE使用event.clientX和event.clientY來表示鼠標(biāo)相對(duì)于window窗口的位置,而不是當(dāng)前 document文檔。在相同的例子中,如果將鼠標(biāo)放置于500*500窗口的中間,clientX和clientY值將均為250。如果向下滾動(dòng)頁面, clientY將仍為250,因?yàn)樗窍鄬?duì)于window窗口來測(cè)量,而不是當(dāng)前的document文檔。因此,在鼠標(biāo)位置中,我們應(yīng)該引入 document文檔body區(qū)域的scrollLeft和scrollTop屬性。最后,IE中document文檔實(shí)際并不在(0,0)的位置,在它周圍有一個(gè)小(通常有2px)邊框,document.body.clientLeft和document.body.clientTop包含了這個(gè)邊框的寬度,從而還需要在鼠標(biāo)位置中引入它們。
幸運(yùn)的是,現(xiàn)在我們擁有了mouseCoords函數(shù),不用再為獲取鼠標(biāo)位置擔(dān)心了。
捕獲鼠標(biāo)的點(diǎn)擊
下一步,我們必須知道鼠標(biāo)何時(shí)點(diǎn)擊及何時(shí)釋放。如果我們跳過這一步,只要你的鼠標(biāo)移動(dòng)經(jīng)過這些元素時(shí),都將產(chǎn)生拖動(dòng)這些元素的效果,這是令人討厭并違反人的直覺的。
在這里,有兩個(gè)函數(shù)可以幫助我們:onmousedown和onmouseup。先前我們已將document.onmousemove指向一個(gè)函數(shù),因此從邏輯上似乎應(yīng)該使document.onmousedown和document.onmouseup都指向函數(shù)。如果我們讓 document.onmousedown指向一個(gè)函數(shù),那么這個(gè)函數(shù)將會(huì)因?yàn)槭髽?biāo)點(diǎn)擊任何元素而執(zhí)行:文本、圖像、表格,等等。我們只想頁面中特定的元素具有被拖放的功能,因此,我們可以通過如下方法實(shí)現(xiàn):
代碼
復(fù)制代碼 代碼如下:
document.onmouseup = mouseUp;
var dragObject = null;
function makeClickable(object) {
object.onmousedown = function() {
dragObject = this;
}
}
function mouseUp(ev) {
dragObject = null;
}
我們現(xiàn)在有了一個(gè)變量dragObject,包含了你點(diǎn)擊的任何元素。當(dāng)你釋放鼠標(biāo)的時(shí)候,dragObject被設(shè)置為空,從而在dragObject非空的時(shí)候,我們需要進(jìn)行拖動(dòng)操作。
移動(dòng)元素
我們現(xiàn)在已經(jīng)知道如何捕獲鼠標(biāo)移動(dòng)和點(diǎn)擊。接下來需要做的就是移動(dòng)任何我們想拖動(dòng)的元素。首先,將一個(gè)元素準(zhǔn)確移動(dòng)到頁面上我們想要的位置,該元素樣式表的position值必須為absolute,這意味著你可以設(shè)置它的style.top或style.left,測(cè)量值相對(duì)于頁面的左上角,因?yàn)槲覀兯械氖髽?biāo)移動(dòng)都是相對(duì)于頁面左上角的,通常都是這樣。
一旦我們?cè)O(shè)置了item.style.position='absolute',接下來就需要改變?cè)撛豻op和left的位置,使它移動(dòng)!
代碼
復(fù)制代碼 代碼如下:
document.onmousemove = mouseMove;
document.onmouseup = mouseUp;
var dragObject = null ;
var mouseOffset = null ;
function getMouseOffset(target, ev) {
ev = ev || window.event;
var docPos = getPosition(target);
var mousePos = mouseCoords(ev);
return {x:mousePos.x - docPos.x, y:mousePos.y - docPos.y} ;
}
function getPosition(e) {
var left = 0;
var top = 0;
while (e.offsetParent) {
left += e.offsetLeft;
top += e.offsetTop;
e = e.offsetParent;
}
left += e.offsetLeft;
top += e.offsetTop;
return {x:left, y:top} ;
}
function mouseMove(ev) {
ev = ev || window.event;
var mousePos = mouseCoords(ev);
if (dragObject) {
dragObject.style.position = 'absolute';
dragObject.style.top = mousePos.y - mouseOffset.y;
dragObject.style.left = mousePos.x - mouseOffset.x;
return false ;
}
}
function mouseUp() {
dragObject = null ;
}
function makeDraggable(item) {
if (!item) return ;
item.onmousedown = function (ev) {
dragObject = this ;
mouseOffset = getMouseOffset(this, ev);
return false ;
}
}
你會(huì)注意到這些代碼是以我們前面的例子為基礎(chǔ)的(參考上篇文章),將它們放置在一起,你將能夠隨意的去移動(dòng)元素。
當(dāng)我們點(diǎn)擊一個(gè)元素時(shí),存儲(chǔ)了另外的一個(gè)變量,mouseOffset。mouseOffset簡單的包含了我們點(diǎn)擊元素的位置信息。如果我們有一張 20*20px的圖像,然后點(diǎn)擊圖像的中間,mouseOffset應(yīng)該是{x:10, y:10}。如果我們點(diǎn)擊圖像的左上角,mouseOffset應(yīng)為{x:0, y:0}。我們?cè)谑髽?biāo)移動(dòng)后的位置信息中用到它。如果我們沒有存儲(chǔ)這個(gè)值,不論你點(diǎn)擊元素的哪一個(gè)位置,元素相對(duì)于鼠標(biāo)的位置都將會(huì)是相同的。
mouseOffset函數(shù)用到了另外一個(gè)函數(shù)getPosition。getPosition目的是返回元素相對(duì)于documemt文檔的坐標(biāo)位置。如果我們簡單的去讀取item.offsetLeft或item.style.left,得到的將是元素相對(duì)于它父元素的位置,而不是document文檔的。在我們的腳本中,所有的元素都是相對(duì)于document文檔的,因此需要這樣做。
要完成獲取元素相對(duì)于document文檔位置的工作,getPosition從它自身的父級(jí)開始,循環(huán)獲取它的left和top的值并累加,這樣我們就得到了我們想要的元素距文檔頂部和左側(cè)的累計(jì)值。
當(dāng)我們獲取了這條信息并移動(dòng)鼠標(biāo)的時(shí)候,mouseMove開始運(yùn)行。首先我們需要保證item.style.position值為absolute,接著,我們將元素移動(dòng)到任何一個(gè)地方,鼠標(biāo)位置都會(huì)減去我們之前記錄的鼠標(biāo)相對(duì)于元素的偏移量。當(dāng)鼠標(biāo)釋放時(shí),dragObject將被設(shè)置為null,并且mouseMove函數(shù)不再做任何事情。
放置元素
我們前面的例子已經(jīng)處理了這個(gè)問題,僅僅是拖動(dòng)一個(gè)元素,然后將它放下。然后,在我們放下元素的時(shí)候通常還有其他的目的,我們以拖動(dòng)元素到垃圾回收站為例,或我們可能想讓該元素和頁面中某個(gè)特定的區(qū)域?qū)R。
不幸的是我們?cè)谶@里進(jìn)入了一個(gè)相對(duì)主要的問題。因?yàn)槲覀冋谝苿?dòng)的元素總是直接處于我們的鼠標(biāo)下,而不可能去引發(fā)mouseover、 mousedown、mouseup或鼠標(biāo)對(duì)頁面中其他元素的操作。如果你移動(dòng)一個(gè)元素到垃圾回收站,你的鼠標(biāo)會(huì)一直在移動(dòng)元素的上方,而不是垃圾回收站。
那么我們?cè)撊绾翁幚磉@個(gè)問題呢?這里有幾種解決方案。在前面所提到的mouseOffset的目的是保證元素總是在鼠標(biāo)下方正確的位置,如果你忽視了這點(diǎn),然后總是使得元素在鼠標(biāo)的右下方,你的鼠標(biāo)將不會(huì)被你正在拖動(dòng)的元素所隱藏,我們也不會(huì)碰到問題。但事實(shí)上往往不會(huì)這樣,為了美觀我們通常要保持元素在鼠標(biāo)的下方。
另外一種選擇是不移動(dòng)你正在拖動(dòng)的元素,你可以改變鼠標(biāo)樣式,來告訴使用者你正在拖動(dòng)一個(gè)元素,直到你將它放置到某個(gè)地方。這解決了我們的問題,但是帶來了和前面一種方案面臨的同樣問題:美觀。
我們最后的一種解決方案既不影響你正在移動(dòng)的元素,也不影響移動(dòng)終點(diǎn)位置上的元素(例如垃圾回收站)。不幸的是,這比前面兩種解決方案的難度更大。我們將要做的是獲得一組我們要放置的目標(biāo),當(dāng)鼠標(biāo)釋放時(shí),我們手工檢查當(dāng)前鼠標(biāo)相對(duì)于每個(gè)目標(biāo)的位置,看鼠標(biāo)是否釋放在這個(gè)目標(biāo)中某一個(gè)目標(biāo)的位置上,如果是的,我們就知道我們已經(jīng)將元素放置在我們的目標(biāo)上了。
代碼
復(fù)制代碼 代碼如下:
/*
All code from the previous example is needed with the exception
of the mouseUp function which is replaced below
*/
var dropTargets = [];
function addDropTarget(dropTarget) {
dropTargets.push(dropTarget);
}
function mouseUp(ev) {
ev = ev || window.event;
var mousePos = mouseCoords(ev);
for (var i = 0; i < dropTargets.length; i ++) {
var curTarget = dropTargets[i];
var targPos = getPosition(curTarget);
var targWidth = parseInt(curTarget.offsetWidth);
var targHeight = parseInt(curTarget.offsetHeight);
if (
(mousePos.x > targPos.x) &&
(mousePos. < (targPos.x + targWidth)) &&
(mousePos.y > targPos.y) &&
(mousePos.y < (targPos.y + targHeight))) {
// dragObject was dropped onto curTarget!
}
}
dragObject = null ;
}
這個(gè)例子中當(dāng)鼠標(biāo)釋放時(shí),我們循環(huán)每個(gè)可能放置元素的目標(biāo),如果鼠標(biāo)指針在目標(biāo)上,我們則擁有了一個(gè)放置元素的事件,通過鼠標(biāo)橫坐標(biāo)大于目標(biāo)元素左側(cè)橫坐標(biāo)(mousePos.x>targPos.x),小于目標(biāo)元素右側(cè)橫坐標(biāo)(mousePos.x<(targPos.x+ targWidth))來判定,對(duì)于Y坐標(biāo)我們做同樣的判斷。如果所有的這些值都返回true,那么我們的鼠標(biāo)就是在目標(biāo)元素的范圍內(nèi)。
整合所有的功能
最后我們使用所有代碼片斷,來創(chuàng)建一個(gè)完整的拖放函數(shù)腳本。我們所要做的第一件事情是DOM操作,如果你對(duì)此并不十分熟悉,可以閱讀《JavaScript Primer on DOM Manipulation》。
接下來的代碼創(chuàng)建容器和容器組,使得在這些容器中可以拖動(dòng)每個(gè)元素,這在本文第二個(gè)demo的基礎(chǔ)上來完成。這段代碼能夠用來重新規(guī)劃元素的順序,將導(dǎo)航窗口放在頁面的左側(cè)或右側(cè),或再加入你所能想到的其他的功能。
我們將使用偽代碼來一步步進(jìn)行講解,將真實(shí)的代碼通過注釋的方式留給讀者查看。
1.當(dāng)文檔第一次被加載時(shí),我們創(chuàng)建一個(gè)名為dragHelper的DIV標(biāo)簽,當(dāng)我們開始移動(dòng)一個(gè)元素的時(shí)候,dragHelper將成為一個(gè)隱藏元素,可以四處移動(dòng)。真實(shí)的元素并不會(huì)被拖動(dòng),僅僅使用insertBefore和appendChild來移動(dòng)。我們?cè)陂_始的時(shí)候隱藏dragHelper。
2.我們創(chuàng)建mouseDown和mouseUp函數(shù)。起初,所有的這些函數(shù)都假設(shè)記錄了鼠標(biāo)按鈕的狀態(tài),以至于iMouseDown變量在鼠標(biāo)按下的時(shí)候?yàn)閠rue,沒有按下的時(shí)候?yàn)閒alse。
3.我們創(chuàng)建一個(gè)全局變量DragDrops,以及一個(gè)函數(shù)CreateDragContainer。DragDrops包含一組相互關(guān)聯(lián)的容器。傳入CreateDragContainer的任何變量(代表容器)被組織成一個(gè)新的集合,使元素能夠在這些容器間自由移動(dòng)。通過setAttribute,CreateDragContainer函數(shù)同樣將各容器中的元素綁定在一起。
4.現(xiàn)在我們的代碼知道每個(gè)元素所在的集合,現(xiàn)在來看mouseMove函數(shù)。mouseMove函數(shù)首先設(shè)置了一個(gè)變量target,表示鼠標(biāo)下面的目標(biāo)元素,如果這個(gè)元素在集合(用getAttribute判斷)中就繼續(xù)下面操作:
4.1.首先,在必要的時(shí)候,我們運(yùn)行一個(gè)簡單的腳本來改變目標(biāo)元素的class屬性,這樣就創(chuàng)造了一個(gè)翻動(dòng)的效果。
4.2.然后我們檢查鼠標(biāo)是否點(diǎn)擊(因?yàn)槲覀兊拇a已經(jīng)運(yùn)行到這里),如果事件發(fā)生:
4.2.1.設(shè)置變量curTarget為當(dāng)前元素。
4.2.2.記錄元素當(dāng)前在文檔中的位置,以便在需要的時(shí)候可以將它的值取回。
4.2.3.將當(dāng)前元素克隆到dragHelper,使得我們能夠移動(dòng)元素的隱藏備份。
4.2.4.因?yàn)樵赿ragHelper中我們完全擁有了拖動(dòng)元素的一個(gè)備份,這個(gè)元素會(huì)始終在鼠標(biāo)下,我們必須移除dragObj屬性,讓代碼知道dragObj已不在集合中。
4.2.5.我們快速記錄集合中每個(gè)元素當(dāng)前的位置、寬度和高度。當(dāng)元素第一次開始被拖動(dòng)時(shí),我們僅需做一次這種工作,否則每當(dāng)鼠標(biāo)移動(dòng)的時(shí)候我們都必須做一次,甚至一秒內(nèi)幾百次。
4.3.如果鼠標(biāo)沒有點(diǎn)擊,要么我們和之前擁有同樣的目標(biāo)元素,要么沒有目標(biāo)元素,不論哪種情況我們都不會(huì)做任何事情。
5.現(xiàn)在我們檢查curTarget變量。curTarget應(yīng)該僅包含一個(gè)被拖動(dòng)的對(duì)象,因此如果它存在,表示我們正在拖動(dòng)一個(gè)元素:
5.1.移動(dòng)隱藏DIV到鼠標(biāo),這個(gè)元素和文章前面所創(chuàng)建的元素一樣能夠被拖動(dòng)。
5.2.然后我們檢查鼠標(biāo)是否存在于當(dāng)前集合中每個(gè)容器中。
5.2.1.如果鼠標(biāo)在某個(gè)容器中,我們檢查容器中的每個(gè)元素,查看我們正拖動(dòng)的元素屬于哪個(gè)位置。
5.2.2.然后我們將所拖動(dòng)的元素放置在容器中另一個(gè)元素的前面,或容器的最后位置。
5.2.3.最后我們確定元素可見。
6.剩下的事情就是捕獲mouseUp事件:
6.1.首先需要隱藏dragHelper:它不再被需要,因?yàn)槲覀儧]有拖動(dòng)任何東西。
6.2.如果拖動(dòng)的元素是可見的,它已經(jīng)存在于任何它所屬的容器中,所有工作已完成。
6.3.如果拖動(dòng)的元素不可見,我們將它放回它原來所在的地方。
您可能感興趣的文章:
- JavaScript實(shí)現(xiàn)網(wǎng)頁對(duì)象拖放功能的方法
- Java實(shí)現(xiàn)鼠標(biāo)拖放功能的方法
- Vue.js實(shí)現(xiàn)拖放效果的實(shí)例
- JavaScript 拖放效果代碼
- Javascript拖拽&拖放系列文章3之細(xì)說事件對(duì)象
- JavaScript 圖片放大鏡(可拖放、縮放效果)
- 基于jQuery實(shí)現(xiàn)的百度導(dǎo)航li拖放排列效果,即時(shí)更新數(shù)據(jù)庫
- 廣泛收集的jQuery拖放插件集合
- asp.net+jquery Gridview的多行拖放, 以及跨控件拖放
- 腳本div實(shí)現(xiàn)拖放功能(兩種)
相關(guān)文章
JS中Require與Import 區(qū)別對(duì)比分析
這篇文章主要為大家介紹了JS中Require與Import 區(qū)別對(duì)比分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03深入理解JavaScript系列(16) 閉包(Closures)
本章我們將介紹在JavaScript里大家經(jīng)常來討論的話題 —— 閉包(closure)。閉包其實(shí)大家都已經(jīng)談爛了。盡管如此,這里還是要試著從理論角度來討論下閉包,看看ECMAScript中的閉包內(nèi)部究竟是如何工作的2012-04-04微信小程序如何調(diào)用新聞接口實(shí)現(xiàn)列表循環(huán)
這篇文章主要介紹了微信小程序如何調(diào)用新聞接口實(shí)現(xiàn)列表循環(huán),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07javascript 進(jìn)度條 實(shí)現(xiàn)代碼
這個(gè)例子是通過測(cè)試的。是真真正正根據(jù)記錄的條數(shù)掛鉤的。2009-07-07