-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Auto scroll solution for list while dragging an element #3482
Comments
https://codesandbox.io/s/mystifying-river-er5r9y?file=/src/Container.tsx My implementation of auto scroll of dragging item hovering on top / bottom bounds of scrollable container. Using |
there is a library for this, which worked great for me |
Is there any solution available for this? without install other library like [react-dnd-scrolling]. |
as far as I understand, there is no solution in the react-dnd lib. you can write the code yourself :) and track the mouse position while dragging |
I'm a bit late to the party but I think I came up with a simple (and "hacky") solution to the problem. Basically you need to create 2 functional React components and place them inside your DnD wrapper. Each one of these components will return a div that is a drop target for the draggable item. Position them at the top and at the bottom of the browser window with the help of CSS. By listening for the isOver event you can detect if a draggable item is (hovering) over these 2 fixed drop targets and scroll the page (or the parent container element) accordingly. To finish, render these divs conditionally by listening for the isDragging event on the draggable items (so they are only there if a user is dragging an item i.e. isDragging === true). Code for reference: const [{ isDragging }, drag, preview] = useDrag({
...
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
...
}); function ScrollTopDropTarget() {
const scrollUp = () => {
window.scrollBy(0, -10);
};
const scrollTopDropRef = useRef(null);
const [{ isOver }, drop] = useDrop({
accept: "your drag item type here",
collect: (monitor) => ({
isOver: monitor.isOver(),
}),
});
drop(scrollTopDropRef);
useEffect(() => {
const intervalId = setInterval(() => {
if (isOver) {
scrollUp();
} else {
clearInterval(intervalId);
}
}, 10);
return () => {
clearInterval(intervalId);
};
}, [isOver]);
return <div ref={scrollTopDropRef} className={styles.scrollTopGuide} />;
} function ScrollBottomDropTarget() {
const scrollDown = () => {
window.scrollBy(0, 10);
};
const scrollBottomDropRef = useRef(null);
const [{ isOver }, drop] = useDrop({
accept: "your drag item type here",
collect: (monitor) => ({
isOver: monitor.isOver(),
}),
});
drop(scrollBottomDropRef);
useEffect(() => {
const intervalId = setInterval(() => {
if (isOver) {
scrollDown();
} else {
clearInterval(intervalId);
}
}, 10);
return () => {
clearInterval(intervalId);
};
}, [isOver]);
return (
<div ref={scrollBottomDropRef} className={styles.scrollBottomGuide} />
);
} .scrollTopGuide {
position: fixed;
z-index: 1010; /* fit to your needs */
top: 0;
left: 0;
right: 0;
height: 60px;
width: 100%;
}
.scrollBottomGuide {
position: fixed;
z-index: 1010; /* fit to your needs */
bottom: 0;
left: 0;
right: 0;
height: 60px;
width: 100%;
} By no means do I insist that this is a perfect solution but at the very least it is one. Feel free to iterate over it and make it better... |
Another solution. You must put this code in a component whose parent has dnd context. The meaning is that the top and the bottom have an area 200 pixels high. We track the offset of the mouse, and if the offset fall into these areas, then a scroll occurs. // inside container with scrollbar
const [dragValue, setDragValue] = useState<boolean>(false);
const dragDropManager = useDragDropManager();
const monitor = dragDropManager.getMonitor();
const timerRef = useRef<NodeJS.Timer>();
const unsubscribeRef = useRef<Unsubscribe>();
const setScrollIntervall = (speed: number, container: HTMLElement) => {
timerRef.current = setInterval(() => {
container.scrollBy(0, speed);
}, 1);
};
useEffect(() => {
if (dragValue) {
unsubscribeRef.current = monitor.subscribeToOffsetChange(() => {
const offset = monitor.getClientOffset();
const container = document.getElementById("main-container"); // container with scrollbar
if (!offset || !container) return;
if (offset.y < container.clientHeight / 2 - 200) {
if (timerRef.current) clearInterval(timerRef.current);
setScrollIntervall(-5, container);
} else if (offset.y > container.clientHeight / 2 + 200) {
if (timerRef.current) clearInterval(timerRef.current);
setScrollIntervall(5, container);
} else if (
offset.y > container.clientHeight / 2 - 200 &&
offset.y < container.clientHeight / 2 + 200
) {
if (timerRef.current) clearInterval(timerRef.current);
}
});
} else if (unsubscribeRef.current) {
if (timerRef.current) clearInterval(timerRef.current);
unsubscribeRef.current();
}
}, [dragValue, monitor]);
useEffect(() => {
const unsubscribe = monitor.subscribeToStateChange(() => {
if (monitor.isDragging()) setDragValue(() => true);
else if (!monitor.isDragging()) setDragValue(() => false);
});
return () => {
unsubscribe();
};
}, [monitor]); |
@BrilliantDecision What is the Unsubscribe type you are using in the unsubscribe ref, please? |
@maxmilianf |
hi, do you mind creating a codesandbox for this? |
hi, do you have a codesandbox example for this? |
@BrilliantDecision worked just fine, thanks! |
I had a different need that required me to use a window as a drag-and-drop container. There was a problem: when I dropped an element on the bottom/top interval, it didn't stop Anyone has better idea? Here is potential solution:
|
Describe the bug
So, as there are no any actual solutions to the last versions of
react-dnd
/React
, I decided to suggest mine. It resolves the issue of auto scroll list while hovering with dragged item on top/bottom bounds of the listReproduction
https://codesandbox.io/s/mystifying-river-er5r9y?file=/src/index.tsx
Steps to reproduce the behavior:
Expected behavior
It should scroll the list according to hover mouse position top / bottom
The text was updated successfully, but these errors were encountered: