HTML5 — 讓拖放變的流行起來

 HTML5 出現之前,頁面元素的拖放需要監聽 mousedown、mouseover 以及 mouseup 等一系列事件,然後改變元素的相對位置來實現這一效果。HTML DnD(Drag-and-Drop)API 的出現,使得拖放變的簡單。但是由於 DnD 尚處在草案階段,各瀏覽器對其規範並未統一,有些事件在不同瀏覽器中會出現不同效果。

要使用 DnD,需要明確兩件事情,一是需要拖動的元素,二是可放置拖動元素的位置。拖放無非是將元素從一個位置拖到另一個位置。

Drag

首先我們需要指定要拖動的元素,設置方式很簡單,給該 DOM 元素設置 draggable 屬性,屬性值設置爲 true。比如這樣:

<img src="images/0.jpg" draggable="true" id="img0"/>

事實上,以上代碼多此一舉了,頁面中的圖片(img)、鏈接(帶 href 的 a 標籤)以及文本默認即爲可拖動。爲了統一,最好還是都加上該 draggable 屬性爲好。draggable 屬性還有兩個值,分別是 false 和 auto,顧名思義,false 即設置爲不可拖動,auto 即爲瀏覽器默認值。

當我們左鍵點擊(按下)可拖動的 DOM 元素,輕輕移動,即觸發 ondragstart 事件,該事件只會觸發一次。通常我們會在 ondragstart 事件中記錄正在被拖動的元素信息(ondrop 的時候好對其進行處理)。比如 demo 中記錄了正在被拖動的元素 id:

for (var i = lis.length; i--; ) {
lis[i].ondragstart = function(e) {
e.dataTransfer.setData('id', e.target.id);
};
}

      ondragstart 事件觸發後,直到拖放事件結束,會一直觸發 ondrag 事件。

Drop

其次我們需要明確被拖動元素可放置的位置,ondragover 事件規定在何處放置被拖動的數據。
默認地,無法將元素放置到其他元素中,如果需要設置允許放置,我們必須阻止對元素的默認處理方式:

var dus = document.querySelector('.dustbin');

dus.ondragover = function(e) {
e.preventDefault();
};

當元素被拖動到某一元素上時,即會觸發後者的 ondrop 事件,如果需要正確觸發 ondrop 事件,還需要取消一些 DnD 事件的默認行爲:

dus.ondrop = function(e) {
// 調用 preventDefault() 來避免瀏覽器對數據的默認處理(drop 事件的默認行爲是以鏈接形式打開)
e.preventDefault();
e.stopPropagation(); // 兼容ff

var id = e.dataTransfer.getData('id')
, node = document.getElementById(id);

node.parentNode.removeChild(node);
};

有些文獻中說要取消 ondragenter() 事件的默認行爲,樓主在實際操作中並未發現這點。

事件

上面已經提到了 DnD 中的三個事件,dragstart、dragover 以及 drop,其實 DnD 還有幾個事件,它們的發生順序是:

dragstart(drag元素) -> drag(drag元素) -> dragenter(drop元素) -> dragover(drop元素) -> dragleave(drop元素) -> drop(drop元素) -> dragend(drag元素)

不難理解,拖放事件開始時觸發 ondragstart 事件,當被拖動元素進入可放置的元素時,觸發 ondragenter 事件(ondragenter 並不是在兩個元素相交時即觸發,而是該被拖拽元素在目標元素上移動一段時間後才觸發),之後一段事件會持續觸發 ondragover 事件(可參考 mouseover),當被拖動元素離開可放置元素的一瞬間,觸發 ondragleave(和 ondragenter 對應) 事件,當鬆開鼠標並且 被拖拽元素正好在可放置元素上時,觸發 ondrop 事件,當拖放事件結束時,觸發 ondragend(和 ondragstart 對應) 事件,無論拖放操作是否成功,均會觸發該事件。

dataTransfer

拖動過程中,回調函數接受的事件參數,有一個 dataTransfer 屬性。它指向一個對象,包含了與拖動相關的各種信息。

dataTransfer 對象主要有兩種方法:getData() 和 setData(),需要注意的是,只有在 dragstart 以及 drop 事件中使用這兩個方法。不難想象,getData() 可以取得由 setData() 保存的值。
setData() 方法的第一個參數,也是 getData() 方法唯一的一個參數,是個字符串,表示保存的數據類型,取值爲 ‘text’ 或 ‘URL’。IE 只定義了 ‘text’ 和 ‘URL’ 兩種有效的數據類型, 而 HTML5 則對此加以擴展,允許指定各種 MIME 類型。

在拖動文本框中的文本時,瀏覽器會自動調用 setData() 方法,將拖動的文本以 ‘text’ 格式保存在 dataTransfer 對象中,類似地,在拖放鏈接或者圖像時,會自動調用 setData() 將 URL 信息 保存,如果有需要,在 drop 事件中可以用 getData() 讀取瀏覽器保存的值。

但是這似乎並沒有什麼卵用,我們在實際開發中多數還是對 DOM 的操作,於是多數情況下我們在 dragstart 事件處理程序中調用 setData(),手工保存自己要傳輸的數據,然後在 drop 事件中讀取, 有點像 jQuery 的 data 事件。

dropEffect 與 effectAllowed

dropEffect 和 effectAllowed 是前面說的 dataTransfer 對象的兩個屬性,有啥用?簡單地說,有兩個用處,一是可以設置元素被拖拽時的鼠標樣式,二是可以設置元素是否可被放置。

這裏我測試了三款瀏覽器,chrome、ff 以及 uc,chrome 和 uc 表現相似。

一般我們將元素脫離原來的位置,鼠標手勢會變成 “禁手”,直到元素被拖到可放置區域上。

但是 ff 不然,在 ff 中,元素在拖動的過程中不會顯示 “禁手”。

當元素被拖到可放置區域上時,默認鼠標手勢如下。

其實通過設置 dropEffect 和 effectAllowed 總共能設置三種鼠標手勢(move, copy,以及 link),分別如下(move 和默認貌似一樣):

需要在 ondragstart 方法中設置 effectAllowed,在 ondragover 方法中設置 dropEffect。具體可以參考 demo代碼。

我們也可以對 dropEffect 和 effectAllowed 的值進行設定,讓某 drop 元素只能放 move 元素,或者 copy 元素等。具體可以看下這篇,HTML5魔法堂:全面理解Drag & Drop API,講的很好。取值也可以參考高程 484 頁。

總之要知道的是,DnD 並不會幫你完成 copy 或者 move 的任何操作,而是需要用戶在 DnD 過程中,記錄需要操作的對象信息,然後在 drop 事件中完成 copy 或者 move 等的操作。

Tricks

還有幾個實踐過程中發現的問題。

將 Demo 在 ff 中打開,圖片拖到空處,會自動在新標籤中打開圖片,儘管我已經在各種事件中加上了 preventDefault(),尚不清楚原因。

如果可拖拽元素,初始在一個可放置元素內部,先把元素拖出去,再放回來,將會觸發 ondrop 事件,但是 e.target 卻是被拖拽的元素。如果放置在其他元素,target 會指向被放置的元素,而不是拖拽元素。這點可以通過判斷 target 元素得到解決。關於這點可以看下 w3cschool 的這個 demo,打開控制檯,將圖片拖出去,再拖回來,控制檯會打印出錯誤,顯然代碼沒有考慮到這一點。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章