過去在網頁裡面實現drag and drop是很麻煩的,需要配合CSS、onmousedown、onmouseup、setInterval()等,來做出元件拖曳的視覺效果。HTML5直接為drag and drop設計了完整的drag and drop機制,利用這些機制就可以很簡單地做出拖曳的效果。
HTML5的拖曳,使用幾個新定義的DOM物件屬性及事件協作:
另外,拖曳事件有一個特別的物件屬性,叫做DataTransfer。這個物件是拖曳時資料傳送及行為控制的重點。定義如下:
interface DataTransfer {
attribute DOMString dropEffect;
attribute DOMString effectAllowed;
readonly attribute DOMStringList types;
void clearData(in optional DOMString format);
void setData(in DOMString format, in DOMString data);
DOMString getData(in DOMString format);
readonly attribute FileList files;
void setDragImage(in Element image, in long x, in long y);
void addElement(in Element element);
};
簡單的drag and drop操作流程如下:
在dragstart,被拖曳物件可以做幾件事:設定effectAllowed、呼叫setData把要傳遞的資料傳給DataTransfer物件、利用setDragImage來指定圖片以設定拖曳的視覺效果、利用addElement來指定用哪個元素作為拖曳的視覺效果。effectAllowed可以設定為'none', 'copy', 'copyLink', 'copyMove', 'link', 'linkMove', 'move', 'all' 與 'uninitialized'。其中,'copyLink', 'copyMove', 'linkMove'表示同時允許兩種效果,'all'表示允許全部效果。
在dragover時,目標物件必須呼叫Event.preventDefault(),讓事件的預設動作停止執行,這樣drop事件才會觸發。同時,在dragover可以設定DataTransfer.dropEffect。他允許的值有'none', 'copy', 'move', 'link',必須與effectAllowed設定的值匹配,才能觸發drop事件。
在drop時,才能使用getData取得用setData傳過來的資料。在dragstart呼叫DataTransfer.setData(format, data),其中format可以指定mime type。不過經過一些測試,比較沒問題的是'text/plain'。用DataTransfer.getData(format)取得資料時,必須用同樣的format參數來取。另外,可以參考DataTransfer.types,然後使用這些傳進來的type字串,用getData()來取得資料。
以下用一個例子來示範:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style>
.container {
background: #336699;
width: 120px;
height: 120px;
border: solid #6699CC 2px;
padding: 3px 3px 3px 3px;
border-radius: 5px;
}
.object {
border: solid #244872 2px;
border-radius: 3px;
text-align: center;
background: #99CCFF;
padding: 2px 2px 2px 2px;
cursor: move;
}
</style>
<script>
function ListIterator(o, cb) {
for(var i=0; i<o.length; i++) {
cb(o.item(i));
}
}
</script>
<div id="panel1" class="container">
<div id="source1" class="object">Test1</div>
<div id="source2" class="object">Test2</div>
<div id="source3" class="object">Test3</div></div>
<br>
<div id="panel2" class="container"></div>
<script>
(function(){
document.ondragover = function(e){e.preventDefault();};
document.ondrop = function(e){e.preventDefault();};
ListIterator(
document.getElementsByClassName('object'),
function(o){o.draggable = true;}
);
ListIterator(
document.getElementsByClassName('object'),
function(o){
o.draggable = true;
o.addEventListener('dragstart', function(e) {
e.dataTransfer.effectAllowed = 'all';
e.dataTransfer.setData('text/plain', this.id);
},false);
}
);
ListIterator(
document.getElementsByClassName('container'),
function(o){
o.addEventListener('dragenter', function(e){
e.preventDefault();
},false);
o.addEventListener('dragover', function(e){
e.preventDefault();
e.dataTransfer.dropEffect = 'copy';
},false);
o.addEventListener('drop', function(e){
e.preventDefault();
e.stopPropagation();
if(e.target!==e.currentTarget) return false;
if(e.dataTransfer.types.length>0) {
for(var i=0; i<e.dataTransfer.types.length; i++) {
if(e.dataTransfer.types[i] === 'text/plain') {
var sourceid = e.dataTransfer.getData('text/plain');
var source = document.getElementById(sourceid);
e.currentTarget.appendChild(source.parentNode.removeChild(source));
}
}
return false;
}
},false);
}
);
})();
</script>
這是在Chrome上執行的效果:
明天來進一步討論drag and drop與其他應用軟體互動。