Drag to Reorder
Reorder list items using native HTML5 drag and drop.
Live Demo
☰
Source
HTML + JavaScript
<div data-component="drag-reorder">
<div id="drag-list" data-list="items" data-key="id">
<template>
<div class="drag-item" draggable="true">
<span>☰</span>
<span data-bind="label"></span>
</div>
</template>
</div>
</div>
<script>
wildflower.component('drag-reorder', {
state: {
items: [
{ id: 1, label: 'First item' },
{ id: 2, label: 'Second item' },
{ id: 3, label: 'Third item' },
{ id: 4, label: 'Fourth item' }
]
},
init() {
const list = this.find('#drag-list');
let dragIdx = -1;
const clearIndicators = () => {
list.querySelectorAll('.drag-over-above, .drag-over-below')
.forEach(el => el.classList.remove('drag-over-above', 'drag-over-below'));
};
list.addEventListener('dragstart', (e) => {
const el = e.target.closest('.drag-item');
if (!el) return;
dragIdx = [...list.querySelectorAll('.drag-item')].indexOf(el);
e.dataTransfer.effectAllowed = 'move';
requestAnimationFrame(() => el.classList.add('dragging'));
});
list.addEventListener('dragover', (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
const target = e.target.closest('.drag-item');
if (!target || dragIdx < 0) return;
clearIndicators();
const rect = target.getBoundingClientRect();
const mid = rect.top + rect.height / 2;
target.classList.add(e.clientY < mid ? 'drag-over-above' : 'drag-over-below');
});
list.addEventListener('drop', (e) => {
e.preventDefault();
clearIndicators();
const target = e.target.closest('.drag-item');
if (!target || dragIdx < 0) return;
const items = [...list.querySelectorAll('.drag-item')];
let dropIdx = items.indexOf(target);
const below = e.clientY >= target.getBoundingClientRect().top
+ target.getBoundingClientRect().height / 2;
if (below && dragIdx > dropIdx) dropIdx++;
else if (!below && dragIdx < dropIdx) dropIdx--;
if (dragIdx === dropIdx) return;
const arr = [...this.items];
const [item] = arr.splice(dragIdx, 1);
arr.splice(dropIdx, 0, item);
this.items = arr;
dragIdx = -1;
});
list.addEventListener('dragend', (e) => {
clearIndicators();
const el = e.target.closest('.drag-item');
if (el) el.classList.remove('dragging');
dragIdx = -1;
});
}
});
</script>
Key Points
- HTML5 drag/drop requires
e.preventDefault()ondragoversynchronously. Native listeners ininit()guarantee this - Event delegation on the list container handles all items with a single set of listeners
- Copy the array, splice, then reassign for clean reactivity with
this.items = arr data-key="id"ensures efficient reconciliation when items move positions