HTML5拖放事件(Drag-and-Drop,DnD)
拖放
拖放是一种常见的特性,即抓取对象以后拖到另一个位置。在 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>