拖放
拖放是一種常見的特性,即抓取對象以后拖到另一個位置。在 HTML5 中,拖放是標准的一部分,任何元素都能夠拖放。
拖放是在“拖放源(drag source)”和“拖放目標(drop target)”之間傳輸數據的用戶界面。下面例子將演示如何創建自定義拖放源和自定義拖放目標,前者傳輸數據而不是其文本內容,后者以某種方式相應拖放數據而不是僅顯示它。
瀏覽器支持
Internet Explorer 9、Firefox、Opera 12、Chrome 以及 Safari 5 支持拖放。
注釋:在 Safari 5.1.2 中不支持拖放。
拖放事件
- DataTransfer 對象:退拽對象用來傳遞的媒介,使用一般為Event.dataTransfer。
- draggable 屬性:就是標簽元素要設置draggable=true,否則不會有效果,例如:
<div title="拖拽我" draggable="true">列表1</div>
- ondragstart 事件:當拖拽元素開始被拖拽的時候觸發的事件,此事件作用在被拖曳元素上
- ondragenter 事件:當拖曳元素進入目標元素的時候觸發的事件,此事件作用在目標元素上
- ondragover 事件:拖拽元素在目標元素上移動的時候觸發的事件,此事件作用在目標元素上
- ondragleave 事件:拖拽元素在目標元素上移動的時候觸發的事件,此事件作用在目標元素上
- ondrop 事件:被拖拽的元素在目標元素上同時鼠標放開觸發的事件,此事件作用在目標元素上
- ondragend 事件:當拖拽完成后觸發的事件,此事件作用在被拖曳元素上
- event.preventDefault() 方法:阻止默認的些事件方法等執行。在ondragover中一定要執行preventDefault(),否則ondrop事件不會被觸發。另外,如果是從其他應用軟件或是文件中拖東西進來,尤其是圖片的時候,默認的動作是顯示這個圖片或是相關信息,並不是真的執行drop。此時需要用用document的ondragover事件把它直接干掉。
- event.setDataTransfer.effectAllowed 屬性:就是拖拽的效果。
- evetn.setDataTransfer.setDragImage() 方法:指定圖片或者文檔元素做拖動時的視覺效果。
DnD總是基於事件且Javascript API包含兩個事件集,一個是在拖放源上觸發,另一個在拖放目標上觸發。所有傳遞給DnD事件處理程序的事件對象都類似鼠標事件對象,另外他擁有dataTransfer屬性。這個屬性引用DataTransfer對象,該對象定義DnD API的方法和屬性。
拖放源時間相當簡單,我們就從他們開始。任何有HTML draggable屬性的文檔元素都是拖放源。當用戶開始用鼠標在拖放源上拖動時,瀏覽器並沒有選擇元素內容,相反,它在這個元素上觸發dragstart事件,這個事件的處理程序就調用dataTransfer.setData()指定當前可用的拖放源數據(和數據類型)。這個時間處理程序也可以設置dataTransfer.effectAllowed來指定支持“移動”、“復制”和“鏈接”傳輸操作中的集中,同時他可以調用dataTransfer.setDragImage()指定圖片或者文檔元素做拖動時的視覺效果。
當放置數據發生時會觸發dragend事件。如果拖放源支持“移動”操作,它就會檢查dataTransfer.dropEffect去看看是否實際執行了移動操作。如果執行了,數據就被傳輸到其他地方,你就應該從拖放源中刪除它。
實例

var whenReady = (function() { // This function returns the whenReady() function var funcs = []; // The functions to run when we get an event var ready = false; // Switches to true when the handler is triggered // The event handler invoked when the document becomes ready function handler(e) { // If we've already run once, just return if (ready) return; // If this was a readystatechange event where the state changed to // something other than "complete", then we're not ready yet if (e.type === "readystatechange" && document.readyState !== "complete") return; // Run all registered functions. // Note that we look up funcs.length each time, in case calling // one of these functions causes more functions to be registered. for(var i = 0; i < funcs.length; i++) funcs[i].call(document); // Now set the ready flag to true and forget the functions ready = true; funcs = null; } // Register the handler for any event we might receive if (document.addEventListener) { document.addEventListener("DOMContentLoaded", handler, false); document.addEventListener("readystatechange", handler, false); window.addEventListener("load", handler, false); } else if (document.attachEvent) { document.attachEvent("onreadystatechange", handler); window.attachEvent("onload", handler); } // Return the whenReady function return function whenReady(f) { if (ready) f.call(document); // If already ready, just run it else funcs.push(f); // Otherwise, queue it for later. } }());
whenReady(function() { var clock = document.getElementById("clock"); // The clock element var icon = new Image(); // An image to drag icon.src = "clock-icon.png"; // Image URL // Display the time once every minute function displayTime() { var now = new Date(); // Get current time var hrs = now.getHours(), mins = now.getMinutes(); if (mins < 10) mins = "0" + mins; clock.innerHTML = hrs + ":" + mins; // Display current time setTimeout(displayTime, 60000); // Run again in 1 minute } displayTime(); // Make the clock draggable // We can also do this with an HTML attribute: <span draggable="true">... clock.draggable = true; // Set up drag event handlers clock.ondragstart = function(event) { var event = event || window.event; // For IE compatability // The dataTransfer property is key to the drag-and-drop API var dt = event.dataTransfer; // Tell the browser what is being dragged. // The Date() constructor used as a function returns a timestamp string dt.setData("Text", Date() + "\n"); // Tell the browser to drag our icon to represent the timestamp, in // browsers that support that. Without this line, the browser may // use an image of the clock text as the value to drag. if (dt.setDragImage) dt.setDragImage(icon, 0, 0); }; }); </script> <style> #clock { /* Make the clock look nice */ font: bold 24pt sans; background: #ddf; padding: 10px; border: solid black 2px; border-radius: 10px; } </style> <h1>Drag timestamps from the clock</h1> <span id="clock"></span> <!-- The time is displayed here --> <textarea cols=60 rows=20></textarea> <!-- You can drop timestamps here -->
拖放目標比拖放源更棘手。任何元素都可以是拖放目標,這不需要像拖放源一樣設置HTML屬性,只需要簡單地定義合適的時間監聽程序。有4個事件在拖放目標上觸發。當拖放對象進入文檔元素時,瀏覽器在這個元素上觸發dragenter事件。拖放目標應該使用dataTransfer.types屬性確定拖放對象的可用數據是否是它能理解的格式。如果檢查成功,拖放目標必須讓用戶和瀏覽器都知道它對防止感興趣。可以通過改變他的邊框或者背景顏色來向用戶反饋。令人吃驚的是,拖放目標通過取消事件來告知瀏覽器它對防止感興趣。如果元素不取消瀏覽器發送給它的draggenter事件,瀏覽器將不會把它作為這次拖放的拖放目標,並不會向它在發送任何事件。但如果拖放目標取消了dragenter事件,瀏覽器將發送dragover事件表示用戶繼續在目標元素上拖動對象。再一次令人吃驚的是,拖放目標必須監聽且取消所有這些事情來表明它對放置感興趣。如果拖放目標向指定它只允許移動、復制和鏈接操作,它應該使用dragover事件處理程序來設置dataTransfer.dropEffect。
如果用戶移動拖放對象離開通過取消事件表明有興趣的拖放目標,那么在拖放目標上將觸發dragleave事件。這個事件的處理程序應該恢復元素的邊框或者背景顏色或者取消任何其他為相應dragenter事件而執行的可視化反饋。遺憾的是,dragenter和draleave事件會冒泡,如果拖放目標內部嵌套元素,想知道dragleave事件表示拖放對象從拖放目標離開到目標外的事件還是到目標內的事件非常困難。
最后,如果用戶把拖放隊形放置在拖放目標上,拖放目標上會觸發drop事件。這個事件的處理程序應該使用dataTransfer.getData()獲取傳輸的數據並做一些適當的處理。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>test</title> <style type="text/css"> *{padding: 0px;margin:0px;} .droppable{ background: yellow; } .dnd{margin:20px;border:2px solid blue;padding: 20px;} </style> <script type="text/javascript"> window.onload = function(){ var lists = document.getElementsByTagName('ul'); var regexp = /\bdnd\b/; for (var i=0;i<lists.length;i++) if (regexp.test(lists[i].className)) dnd(lists[i]); function dnd(list){ var original_class = list.className; var entered = 0; list.ondragenter = function(e){ e = e || window.event; var from = e.relatedTarget; entered++; if ((from &&!isChild(from,list)) || entered ==1) { var dt = e.dataTransfer; var types = dt.types; if (!types ||(types.contains&&types.contains("text/plain"))||(types.indexOf &&types.indexOf("text/plain")!=-1)) { list.className = original_class + " droppable"; return false; } return; } return false; }; list.ondragover = function(e){return false;} list.ondragleave = function(e){ e = e || window.event; var to = e.relatedTarget; entered--; if (to && !isChild(to,list) || entered <= 0) { list.className = original_class; entered = 0; } return false; }; list.ondrop = function(e){ e = e || window.event; var dt = e.dataTransfer; var text = dt.getData("Text"); if (text) { var item = document.createElement("li"); item.draggable = true; item.appendChild(document.createTextNode(text)); list.appendChild(item); list.className = original_class; entered = 0; return false; } }; var items = list.getElementsByTagName("li"); for (var i=0;i<items.length;i++) items[i].draggable = true; list.ondragstart = function(e){ var e = e || window.event; var target = e.target || e.srcElemnt; if (target.tagName !=="LI") return false; var dt = e.dataTransfer; dt.setData("Text", target.innerText || target.textContent); dt.effectAllowed = "move"; }; list.ondragend = function(e){ e = e || window.event; var target = e.target || e.srcElemnt; if (e.dataTransfer.dropEffect ==="move") target.parentNode.removeChild(target); } function isChild(a,b){ for(; a; a = a.parentNode) if (a === b) return true; return false; } } } </script> </head> <body> <ul class="dnd"> <li>測試測試測試測試-1</li> <li>測試測試測試測試-2</li> <li>測試測試測試測試-3</li> <li>測試測試測試測試-4</li> </ul> <ul class="dnd"> <li>測試測試測試測試-5</li> <li>測試測試測試測試-6</li> <li>測試測試測試測試-7</li> <li>測試測試測試測試-8</li> </ul> <ul class="dnd"> <li>測試測試測試測試-9</li> <li>測試測試測試測試-10</li> <li>測試測試測試測試-11</li> <li>測試測試測試測試-12</li> </ul> </body> </html>