Adding an AutoScroll with react-dnd

Add auto scroll behaviour in react-dnd

Recently, when I was working on a drag and drop feature, I stumble upon an issue whereby react-dnd itself doesn’t cater auto scroll behavior by default if the content length is beneath the visible view.

I got stumped at first as I have never encounter this type of issue due to lack of experience. After searching thru and using AI-tools, I found an answer that was pretty suitable for general cases.

The code snippet is as below:

  • requestAnimationFrame is a method that tells the browser you wish to perform an animation. It requests the browser to call a user-supplied callback function before the next repaint. (Definition from MDN document)
  • cancelAnimationFrame is a method cancels an animation frame request previously scheduled through a call to requestAnimationFrame (Definition from MDN document)
  • The general gist is it will get the container height (especially its top and bottom coordinates) and track the mouse pointer coordinates to determine when it should scroll up or down.
  • The threshold is to make sure the scrolling doesn’t simply trigger as it may easily get out of control if scrolling is trigger due to minimal movement.
const AutoScroller = () => {
  const { isDragging, pointer } = useDragLayer((monitor) => ({
    isDragging: monitor.isDragging(),
    pointer: monitor.getClientOffset(),
  }));
  const pointerRef = useRef(pointer);

  useEffect(() => {
    pointerRef.current = pointer;
  }, [pointer]);

  useEffect(() => {
    if (!isDragging) return;

    let animationFrameId: number;
    const scrollContainer = document.querySelector('.<CONTAINER_CLASSNAME>');

    const tick = () => {
      if (!scrollContainer || !pointerRef.current) {
        animationFrameId = requestAnimationFrame(tick);
        return;
      }

      const { top, bottom } = scrollContainer.getBoundingClientRect();
      const { y } = pointerRef.current;
      const threshold = 150; // boundary to trigger scrolling
      const maxSpeed = 20; // max speed of scrolling

      if (y < top + threshold) {
        const intensity = (top + threshold - y) / threshold;
        scrollContainer.scrollTop -= maxSpeed * intensity;
      } else if (y > bottom - threshold) {
        const intensity = (y - (bottom - threshold)) / threshold;
        scrollContainer.scrollTop += maxSpeed * intensity;
      }

      animationFrameId = requestAnimationFrame(tick);
    };

    tick();

    return () => cancelAnimationFrame(animationFrameId);
  }, [isDragging]);

  return null;
};

In order to use it, one must insert it as an element within the <DndProvider> from react-dnd. Examples as below:

<DndProvider backend={HTML5Backend}>
	<AutoScroller />
	...
</DndProvider>

Hope it helps!