Drag a row in a to-do list and the others slide to make room, landing it in a new slot. The visible part is animation; the brain is a tiny bit of geometry: as you drag, figure out which index the item should occupy right now. The trick that makes it feel right — no flicker, no jitter at boundaries — is using each item's midpoint: a neighbor only gets displaced once your cursor crosses the middle of it, not the moment you touch its edge. This is the core of SortableJS, dnd-kit, and every reorderable list.
Implement dragAndDropSortable(rects, fromIndex, pointerY) — pure math, no DOM.
function dragAndDropSortable(rects, fromIndex, pointerY) {
return { toIndex, order }; // where it lands + the resulting index order
}
// 4 rows, 50px tall: midpoints 25, 75, 125, 175
dragAndDropSortable(rows, 0, 80); // dragging row 0, cursor at y=80 (past midpoint 75)
// { toIndex: 1, order: [1, 0, 2, 3] }
dragAndDropSortable(rows, 0, 999); // dragged far below everything
// { toIndex: 3, order: [1, 2, 3, 0] }
pointerY crosses its midpoint (top + height/2), so hovering its top half doesn't swap yet. That hysteresis is what stops the flicker.toIndex is the number of other items (exclude the dragged one) whose midpoint sits above pointerY.toIndex is expressed in the list with the dragged item removed, i.e. the second argument to a move(from, to) splice; order is the reordered array of original indices.Unlock the solution & editor
Runnable editor + tests
Solve in the browser with instant Jest feedback.
Detailed solutions
Walkthroughs, edge cases, and complexity notes.
Multi-framework variants
React, Vue, Vanilla, Angular — same question, different stacks.