[HTML5] Drag and Drop API
未閱讀:Drag & Drop Elements with Vanilla JavaScript and HTML @ alligator
HTML5 中的 Drag & Drop API 可以讓我們在瀏覽器中做到拖曳元素、排序元素、或者是讓使用者透過拖拉的方式把要上傳的檔案拉到瀏覽器當中。
在學習 HTML5 Drag & Drop API 時,最重要的是要去區分 Drag Source
和 Drop Target
,因為它們會需要各自去監聽不同的事件。
假設我們要把下圖中藍色的圓從左邊的區域移到右邊的區域,那麼:
- Drag Source 指的是被點擊要拖曳的物件,也就是藍色的圓,通常是一個 element。
- Drop Target 指的是拖曳的物件被放置的區域,也就是右邊的綠色區域,通常是一個 div container。
前置動作
在開始實做前,有一些是需要知道和注意的前置動作。
事件(Event)
Drag & Drop 提供的間事件主要包含 dragstart
, drag
, dragend
, dragenter
, dragover
, dragleave
, drop
,其中有些是針對 Drag Source 的,有些則是針對 Drop Target 的,整理如下表:
Drag Source | Drop Target |
---|---|
dragstart | |
drag | dragenter |
dragover | |
dragleave | |
drop | |
dragend |
- drag:在 drag source 被拖曳時會持續被觸發。
- dragover:當拖曳的 drag source 在 drop target 上方時會持續被觸發。
記得要針對
dragover
取消預設行為(preventDefault),否則可能無法正確觸發 drop 事件。
CSS Style
針對要被拖曳的元素(Drag Source)可以透過 CSS 的屬性設定,避免讓使用者在拖曳該元素時選取到裡面的內容:
[draggable='true'] {
/*
To prevent user selecting inside the drag source
*/
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
HTML Attribute
針對能夠被拖曳的元素,在其 HTML 標籤上添加屬性 draggable="true"
:
<div id="drag-source" draggable="true"></div>
Drag and Drop Basic
示範如何將一個物件從一個 container (#source-container
) 內拖曳到另一個 container (#target-container
)內:
HTML
針對可以被拖曳的元素加上 draggable="true"
:
<div id="drag-drop-basic">
<div id="source-container">
<div id="drag-source" draggable="true"></div>
</div>
<div id="target-container"></div>
</div>
JavaScript
針對要被拖曳的元素(dragSource) 監聽 dragstart
事件,並且把要傳遞給 dropTarget
的資料透過 setData
加以設定:
let dragSource = document.querySelector('#drag-source');
dragSource.addEventListener('dragstart', dragStart);
function dragStart(e) {
e.dataTransfer.setData('text/plain', e.target.id);
}
針對要被置放的容器 dropTarget
監聽 drop
事件,來處理當使用者放掉的時候要執行的行為,並透過 getData
來取得傳遞的資料;監聽 dragenter
和 dragover
事件來避免預設行為:
let dropTarget = document.querySelector('#target-container');
dropTarget.addEventListener('drop', dropped);
dropTarget.addEventListener('dragenter', cancelDefault);
dropTarget.addEventListener('dragover', cancelDefault);
function dropped(e) {
console.log('dropped');
cancelDefault(e);
let id = e.dataTransfer.getData('text/plain');
e.target.appendChild(document.querySelector('#' + id));
}
function cancelDefault(e) {
e.preventDefault();
e.stopPropagation();
return false;
}
Demo & Source Code
Drag and Drop Basic @ CodePen
Drag and Drop with multiple sources in multiple containers
HTML
在能夠被拖放的容器上加上 data-role="drag-drop-container"
屬性:
<div id="drag-drop-basic">
<div id="source-container" data-role="drag-drop-container">
<div id="drag-source" draggable="true"></div>
</div>
<div id="target-container" data-role="drag-drop-container"></div>
</div>
JavaScript
允許多個可拖曳的物件:
// Allow multiple draggable items
let dragSources = document.querySelectorAll('[draggable="true"]');
dragSources.forEach((dragSource) => {
dragSource.addEventListener('dragstart', dragStart);
});
function dragStart(e) {
e.dataTransfer.setData('text/plain', e.target.id);
}
允許多個可置放的容器:
// Allow multiple dropped targets
let dropTargets = document.querySelectorAll('[data-role="drag-drop-container"]');
dropTargets.forEach((dropTarget) => {
dropTarget.addEventListener('drop', dropped);
dropTarget.addEventListener('dragenter', cancelDefault);
dropTarget.addEventListener('dragover', cancelDefault);
});
function dropped(e) {
cancelDefault(e);
let id = e.dataTransfer.getData('text/plain');
e.target.appendChild(document.querySelector('#' + id));
}
function cancelDefault(e) {
e.preventDefault();
e.stopPropagation();
return false;
}
Demo & Source Code
Drag and Drop in Multiple Container @ CodePen
修改拖曳時的 CSS 樣式
我們新增兩個 CSS 樣式分別套用在被拖曳的物件和被放置的容器上:
// For drag sources
.dragging {
opacity: 0.25;
}
// For drop target
.hover {
background-color: rgba(0, 191, 165, 0.04);
}
針對物件本身,我們在開始拖曳時添加樣式,結束拖曳時移除樣式:
function dragStart(e) {
this.classList.add('dragging');
e.dataTransfer.setData('text/plain', e.target.id);
}
function dragEnd(e) {
this.classList.remove('dragging');
}
針對容器,我們在進入容器時添加樣式,在離開或放置後移除樣式:
function dropped(e) {
let id = e.dataTransfer.getData('text/plain');
e.target.appendChild(document.querySelector('#' + id));
this.classList.remove('hover');
}
function dragOver(e) {
this.classList.add('hover');
}
function dragLeave(e) {
this.classList.remove('hover');
}
Demo & Source Code
Drag and Drop with CSS Style @ CodePen
製作可拖拉排序的清單
下面的範例中有用到 jQuery:
HTML
建立一個清單:
<ul id="items-list" class="moveable">
<li>One</li>
<li>Two</li>
<li>Three</li>
<li>Four</li>
</ul>
JavaScript
先利用 document.querySelectorAll
將所有清單中的元素選取起來:
let items = document.querySelectorAll('#items-list > li');
將清單中的每一個元素都加上 draggable="true"
的屬性,並且監聽相關事件:
items.forEach((item) => {
$(item).prop('draggable', true);
item.addEventListener('dragstart', dragStart);
item.addEventListener('drop', dropped);
item.addEventListener('dragenter', cancelDefault);
item.addEventListener('dragover', cancelDefault);
});
取得被拖曳物件的 index 值:
function dragStart(e) {
var index = $(e.target).index();
e.dataTransfer.setData('text/plain', index);
}
放下(drop)的時候要把原本 index 的元素移除(remove)掉
function dropped(e) {
cancelDefault(e);
// get new and old index
let oldIndex = e.dataTransfer.getData('text/plain');
let target = $(e.target);
let newIndex = target.index();
// remove dropped items at old place
let dropped = $(this).parent().children().eq(oldIndex).remove();
// insert the dropped items at new place
if (newIndex < oldIndex) {
target.before(dropped);
} else {
target.after(dropped);
}
}
function cancelDefault(e) {
e.preventDefault();
e.stopPropagation();
return false;
}
Demo & Source Code
Drag and Drop Sortable List @ CodePen
其他
將檔案拖曳至瀏覽器
DataTransfer.files;
DataTransfer.files @ MDN
Drag and Drop Effect
DataTransfer 屬性 | 對象 | 事件 |
---|---|---|
effectAllowed | drag sources | dragstart |
dropEffect | drag target | dragover |
effectAllowed
是針對 drag sources 的 dragstart 事件;dropEffect
則是針對 drop target 的 dragover 事件。
如果 effectAllowed 和 dropEffect 中設定的類型不同,則無法將該 source 拖曳到 target。
/**
* copy, move, link, none
**/
DataTransfer.effectAllowed; // 設定在 dragstart event,允許拖曳的效果
DataTransfer.dropEffect; // 設定在 dragover event,當檔案拖曳進來時顯示的效果
DataTransfer.dropEffect @ MDN DataTransfer.effectAllowed @ MDN
客制化拖曳圖片
套用在 dragStart
事件中:
DataTransfer.setDragImage(img, x-offset, y-offset)
function dragStart(e) {
let img = new Image();
img.src = 'example.gif';
e.dataTransfer.setDragImage(img, 10, 10);
}
NOTICE
在 FireFox 中的 dragstart
事件可能需要將 setData
設為空:
function dragstart(e) {
e.dataTransfer.setData('text/plain', '');
}
其他
一個寫得很好的清單拖拉範例,裡面有用到 document.elementFromPoint(x, y)
這個 Web API,謝謝 @hannahpun 分享:
參考
- HTML 拖放 API @ MDN
- Data Transfer @ MDN
HTML Fundamentals @ Pluralsight
- Introduction to Drag and Drop Events
- Drag and Drop Events in Detail
- Safari Support
- Draggable CSS Style
- Drag and Drop Basics
- Using Role Selectors
- Events in Action
- Styling Drag Sources and Drop Targets
- Implementing Drag and Drop Sortable List
- Drag and Drop Data Transfer Types
- Dropping Files from The Client into The Web Browser
- Setting and Enforcing Drag and Drop Effects
- Customizing The Drag Cursor Image
- Implementing A Drag and Drop Module Demo and Markup
- Implementing A Drag and Drop Module JavaScript