拖曳事件 (DragEvent)
拖曳事件(DragEvent / HTML5 Drag and Drop)
TL;DR三個核心事件:dragstart(來源)、dragover(目標,必須 preventDefault())、drop(目標完成接收)。沒呼叫 preventDefault() 的話 drop 不會觸發,這是最常踩的雷。
三個必懂事件
| 事件 | 觸發時機 | 觸發在哪 | 必做 |
|---|---|---|---|
dragstart | 使用者開始拖拉一個 draggable="true" 的元素 | 來源元素 | e.dataTransfer.setData() 存資料 |
dragover | 拖拉中經過某元素的「上方持續觸發」 | 目標元素 | 必須 e.preventDefault(),否則 drop 不觸發 |
drop | 使用者放開滑鼠 | 目標元素 | e.preventDefault() + e.dataTransfer.getData() 取資料 |
完整範例(原生 JS)
<ul id="list">
<li draggable="true" data-index="0">A</li>
<li draggable="true" data-index="1">B</li>
<li draggable="true" data-index="2">C</li>
</ul>
const list = document.getElementById('list');
list.addEventListener('dragstart', (e) => {
// 1. 標記哪個 item 被拖
e.dataTransfer.setData('text/plain', e.target.dataset.index);
e.dataTransfer.effectAllowed = 'move';
});
list.addEventListener('dragover', (e) => {
e.preventDefault(); // ⚠️ 必須!否則 drop 不會觸發
e.dataTransfer.dropEffect = 'move';
});
list.addEventListener('drop', (e) => {
e.preventDefault();
const fromIndex = parseInt(e.dataTransfer.getData('text/plain'));
const toIndex = parseInt(e.target.dataset.index);
// ...交換邏輯
});
React 版本
function SortableList({ items, onReorder }: Props) {
const [draggedIndex, setDraggedIndex] = useState<number | null>(null);
return (
<ul>
{items.map((item, i) => (
<li
key={item.id}
draggable
onDragStart={(e) => {
setDraggedIndex(i);
e.dataTransfer.effectAllowed = 'move';
}}
onDragOver={(e) => e.preventDefault()}
onDrop={(e) => {
e.preventDefault();
if (draggedIndex !== null && draggedIndex !== i) {
onReorder(draggedIndex, i);
}
setDraggedIndex(null);
}}
>
{item.label}
</li>
))}
</ul>
);
}
原始來源Stack Overflow — Move items up in a list with click with reactjs
常見地雷
🪤 dragover 沒 preventDefault → drop 永遠不觸發
這是 80% 拖曳功能爆掉的原因。瀏覽器預設不允許 drop,要 listener 主動「我接受」才會觸發 drop 事件。
🪤 dataTransfer.setData('text/plain', ...) 必須是字串
不能直接傳 object,要先 JSON.stringify:
e.dataTransfer.setData('application/json', JSON.stringify(item));
// drop 時:
const item = JSON.parse(e.dataTransfer.getData('application/json'));
🪤 行動裝置(Touch)不會觸發 DragEvent
HTML5 Drag and Drop 只支援滑鼠,手機觸控完全不觸發。要支援手機:
- 用
dnd-kit(React)/Sortable.js(原生)等套件,內部會處理 Touch + Pointer events - 自己用
pointerdown/pointermove/pointerup自訂
實務建議
用套件還是原生?
- 簡單的列表排序:原生夠用,4-5 個 listener 就完成
- 複雜場景(虛擬滾動、跨容器、多選、行動裝置):直接用套件,別自己寫
- React:
@dnd-kit/core、react-beautiful-dnd(已停更)- 原生:
Sortable.js