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 上方時會持續被觸發。
記得要針對被拖曳的物件取消預設行為(default)
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
Demo & Source Code
See the Pen Drag and Drop Basic by PJCHEN (@PJCHENder) on CodePen.
示範如何將一個物件從一個 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) {
console.log('dragStart')
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
}
Drag and Drop with multiple sources in multiple containers
Demo & Source Code
在這個範例中,我們可以把 drag sources 來回拖放於左右兩邊的 drop target:See the Pen Drag and Drop in Multiple Container by PJCHEN (@PJCHENder) on CodePen.
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
}
修改拖曳時的 CSS 樣式
Demo & Source Code
See the Pen Drag and Drop with CSS Style by PJCHEN (@PJCHENder) on CodePen.
我們新增兩個 CSS 樣式分別套用在被拖曳的物件和被放置的容器上:
// For drag sources
.dragging {
opacity: .25;
}
// For drop target
.hover {
background-color: rgba(0,191,165,.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
See the Pen Drag and Drop Sortable List by PJCHEN (@PJCHENder) on 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
}
其他
將檔案拖曳至瀏覽器
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 事件。
如果 effectAllowd 和 dropEffect 中設定的類型不同,則無法將該 source 拖曳到 target。
/**
* copy, move, link, none
**/
DataTransfer.effectAllowed // 設定在 dragstart event,允許拖曳的效果
DataTransfer.dropEffect // 設定在 dragover event,當檔案拖曳進來時顯示的效果
客制化拖曳圖片
套用在 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 分享:
See the Pen Vanilla JavaScript - Drag Sort by Fitri Ali (@fitri) on CodePen.
參考
- 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
0 意見:
張貼留言