Skip to content
eloxi
v0.12.0

Drag to reorder

Item 1
Item 2
Item 3
Item 4

HTML

<div class="list">
<div class="item" data-vel-plugin="DragToReorderPlugin" data-vel-view="item">
Item 1
</div>
<div class="item" data-vel-plugin="DragToReorderPlugin" data-vel-view="item">
Item 2
</div>
<div class="item" data-vel-plugin="DragToReorderPlugin" data-vel-view="item">
Item 3
</div>
<div class="item" data-vel-plugin="DragToReorderPlugin" data-vel-view="item">
Item 4
</div>
</div>

CSS

.list {
display: flex;
flex-direction: column;
gap: 10px 0;
width: 100%;
max-width: 200px;
}
.item {
font-size: 18px;
background: whitesmoke;
padding: 8px 10px;
border-radius: 5px;
color: #222;
font-weight: 500;
user-select: none;
-webkit-user-select: none;
touch-action: none;
-webkit-touch-action: none;
will-change: transform;
box-shadow: 1px 2px 10px rgba(0, 0, 0, 0.2);
}

JavaScript

API Style:
Functions
Classes
import { createApp, DragEventPlugin, DragEvent } from 'veloxi'
const DragToReorderPlugin = (context) => {
const dragEventPlugin = context.useEventPlugin(DragEventPlugin)
dragEventPlugin.on(DragEvent, onDrag)
let items
let draggingItem = null
const itemIdSlotMap = new Map()
const slotPositionMap = new Map()
context.setup(() => {
items = context.getViews('item')
items.forEach((item, index) => {
assignItemToSlot(item, index)
dragEventPlugin.addView(item)
item.position.animator.set('dynamic')
slotPositionMap.set(index, item.position.y)
})
})
function onDrag(event) {
if (event.isDragging) {
draggingItem = event.view
draggingItem.position.set({ x: event.x, y: event.y })
draggingItem.styles.zIndex = '2'
updateItemPositions()
} else {
if (!draggingItem) return
draggingItem.styles.zIndex = ''
draggingItem.position.set({
x: draggingItem.position.initialX,
y: getSlotPositionForItem(draggingItem)
})
draggingItem = null
}
}
function updateItemPositions() {
items
.filter((item) => item.id !== draggingItem?.id)
.forEach((item) => {
if (!draggingItem) return
const draggingItemSlot = itemIdSlotMap.get(draggingItem.id)
const itemSlot = itemIdSlotMap.get(item.id)
if (draggingItemSlot === undefined || itemSlot === undefined) return
const draggingItemPosition = draggingItem.position.y
const itemPosition = getSlotPositionForItem(item)
if (
draggingItemSlot < itemSlot &&
draggingItemPosition + draggingItem.size.height >
itemPosition + item.size.height / 2
) {
assignItemToSlot(item, draggingItemSlot)
assignItemToSlot(draggingItem, itemSlot)
item.position.set({
y: slotPositionMap.get(draggingItemSlot)
})
} else if (
draggingItemSlot > itemSlot &&
draggingItemPosition < itemPosition + item.size.height / 2
) {
assignItemToSlot(item, draggingItemSlot)
assignItemToSlot(draggingItem, itemSlot)
item.position.set({
y: slotPositionMap.get(draggingItemSlot)
})
}
})
}
function assignItemToSlot(item, slot) {
itemIdSlotMap.set(item.id, slot)
}
function getSlotPositionForItem(item) {
const slot = itemIdSlotMap.get(item.id)
return slotPositionMap.get(slot)
}
}
DragToReorderPlugin.pluginName = 'DragToReorderPlugin'
const app = createApp()
app.addPlugin(DragToReorderPlugin)
app.run()
import { createApp, DragEventPlugin, DragEvent, Plugin, View } from 'veloxi'
class DragToReorderPlugin extends Plugin {
static pluginName = 'DragToReorderPlugin'
dragEventPlugin = this.useEventPlugin(DragEventPlugin)
items!: Array<View>
draggingItem: View | null = null
itemIdSlotMap = new Map()
slotPositionMap = new Map()
setup() {
this.items = this.getViews('item')
this.items.forEach((item, index) => {
this.assignItemToSlot(item, index)
this.dragEventPlugin.addView(item)
item.position.animator.set('dynamic')
this.slotPositionMap.set(index, item.position.y)
})
this.dragEventPlugin.on(DragEvent, this.onDrag.bind(this))
}
onDrag(event: DragEvent) {
if (event.isDragging) {
this.draggingItem = event.view
this.draggingItem.position.set({ x: event.x, y: event.y })
this.draggingItem.styles.zIndex = '2'
this.updateItemPositions()
} else {
if (!this.draggingItem) return
this.draggingItem.styles.zIndex = ''
this.draggingItem.position.set({
x: this.draggingItem.position.initialX,
y: this.getSlotPositionForItem(this.draggingItem)
})
this.draggingItem = null
}
}
updateItemPositions() {
this.items
.filter((item) => item.id !== this.draggingItem?.id)
.forEach((item) => {
if (!this.draggingItem) return
const draggingItemSlot = this.itemIdSlotMap.get(this.draggingItem.id)
const itemSlot = this.itemIdSlotMap.get(item.id)
if (draggingItemSlot === undefined || itemSlot === undefined) return
const draggingItemPosition = this.draggingItem.position.y
const itemPosition = this.getSlotPositionForItem(item)
if (
draggingItemSlot < itemSlot &&
draggingItemPosition + this.draggingItem.size.height >
itemPosition + item.size.height / 2
) {
this.assignItemToSlot(item, draggingItemSlot)
this.assignItemToSlot(this.draggingItem, itemSlot)
item.position.set({
y: this.slotPositionMap.get(draggingItemSlot)
})
} else if (
draggingItemSlot > itemSlot &&
draggingItemPosition < itemPosition + item.size.height / 2
) {
this.assignItemToSlot(item, draggingItemSlot)
this.assignItemToSlot(this.draggingItem, itemSlot)
item.position.set({
y: this.slotPositionMap.get(draggingItemSlot)
})
}
})
}
assignItemToSlot(item: View, slot: number) {
this.itemIdSlotMap.set(item.id, slot)
}
getSlotPositionForItem(item: View) {
const slot = this.itemIdSlotMap.get(item.id)
return this.slotPositionMap.get(slot)
}
}
const app = createApp()
app.addPlugin(DragToReorderPlugin)
app.run()