'How to handle multidirectional drag'n' drop list re-order with react framer motion?
I have forked React framer motion's drag'n'drop list re-order example. It works like a charm with a vertical list. I have also made it work in a horizontal way by simply passing the delta.x.translate value of the dragged div to the main function. But I'm stuck at handling multidirectional re-ordering.
Here is a sandbox: https://codesandbox.io/s/framer-motion-dragn-list-horizontal-3g9he?file=/src/App.js
The code looks like this:
export default function App() {
const [items, setItems] = useState([
"eat",
"work",
"sleep",
"repeat",
"cook",
"doStuff"
]);
return <Container items={items} onChangeOrder={setItems} />;
}
function Container({ items, onChangeOrder }) {
const [updatePosition, updateOrder] = usePositionReorder(
items,
onChangeOrder
);
return (
<ul>
{items.map((item, i) => (
<Item
key={item}
item={item}
index={i}
updatePosition={updatePosition}
updateOrder={updateOrder}
/>
))}
</ul>
);
}
function Item({ index, item, updatePosition, updateOrder }) {
const [isDragging, setDragging] = useState(false);
const ref = useMeasurePosition((pos) => updatePosition(index, pos));
return (
<li>
<motion.div
ref={ref}
layout
initial={false}
drag={true}
onDragStart={() => setDragging(true)}
onDragEnd={() => setDragging(false)}
onViewportBoxUpdate={(_viewportBox, delta) => {
isDragging &&
updateOrder(index, delta.y.translate, delta.x.translate);
}}
>
{item}
</motion.div>
</li>
);
}
The main function is:
import { useRef } from "react";
import { distance } from "popmotion";
const arrayMoveMutate = (array, from, to) => {
const startIndex = from < 0 ? array.length + from : from;
if (startIndex >= 0 && startIndex < array.length) {
const endIndex = to < 0 ? array.length + to : to;
const [item] = array.splice(from, 1);
array.splice(endIndex, 0, item);
}
};
const move = (array, from, to) => {
array = [...array];
arrayMoveMutate(array, from, to);
return array;
};
const clamp = (min, max, v) => Math.min(Math.max(v, min), max);
export function usePositionReorder(order, setOrder) {
const positions = useRef([]).current;
const updatePosition = (i, offset) => (positions[i] = offset);
const updateOrder = (i, dragYOffset, dragxOffset) => {
const targetIndex = findIndex(i, dragYOffset, dragxOffset, positions);
if (targetIndex !== i) setOrder(move(order, i, targetIndex));
};
return [updatePosition, updateOrder];
}
export const findIndex = (i, yOffset, xOffset, positions) => {
const buffer = 30;
let target = i;
const { top, height } = positions[i];
const bottom = top + height;
// If moving down
if (yOffset > 0) {
const nextItem = positions[i + 1];
if (nextItem === undefined) return i;
const swapOffset =
distance(bottom, nextItem.top + nextItem.height / 2) + buffer;
if (yOffset > swapOffset) return (target = i + 1);
}
// If moving left
//do stuff
//if moving right
//do stuff
// If moving up
else if (yOffset < 0) {
const prevItem = positions[i - 1];
if (prevItem === undefined) return i;
const prevBottom = prevItem.top + prevItem.height;
const swapOffset = distance(top, prevBottom - prevItem.height / 2) + buffer;
if (yOffset < -swapOffset) target = i - 1;
}
return clamp(0, positions.length, target);
};
The function should properly sort the list in both vertical and horizontal way. It should also work if I make a diagonal movement (from bottom right to somewhere around top-left for exemple).
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
