简体   繁体   中英

scrollBy execution in recursive function in ReactJs doesn't work as expected

As a part of my journey of creating own realization of drag&drop I faced with the problem of autoscrolling while dragging item. For now I just try to realize primitive autoscrolling: when mouse enters the bottom-trigger area the page should start scrolling slowly, but it's scrolled by 10px and stopped, while I leave trigger area. What's the couse of these problem, does scrollBy rewrites each other till the end of recursion? If yes, how can I avoid it?

Code example(High order component AutoScroll):

export interface IAutoScrollProps {
 children: ReactNode;
}

const AutoScroll = ({ children }: IAutoScrollProps) => {
 const bottomTriggerClass = `${styles['bottom-trigger']} ${styles['trigger']} 
 ${styles['horizontal']}`;

 const overBottomTrigger = useRef(false);

 const scrollBottom: () => void | NodeJS.Timeout = useCallback(() => {
  window.scrollBy(0, 10);

  if (!overBottomTrigger.current) return;

  if (document.body.scrollHeight - window.innerHeight - window.scrollY < 20) return;

  return setTimeout(scrollBottom, 0);
 }, []);

const onBottomTriggerMouseEnter: (e: React.MouseEvent) => void = useCallback(
(e) => {
  console.log('enter');

  overBottomTrigger.current = true;
  scrollBottom();
},[scrollBottom]);

const onBottomTriggerMouseLeave: (e: React.MouseEvent) => void = useCallback((e) => {
 console.log('leave');

 overBottomTrigger.current = false;
}, []);

return (
  <div>
   <section className="autoScroll-container p-relative">
     {children}
   </section>
   <span
    className={bottomTriggerClass}
    onMouseEnter={onBottomTriggerMouseEnter}
    onMouseLeave={onBottomTriggerMouseLeave}
   ></span>
  </div>
 );
};

export default AutoScroll;

程序行为示例

The problem is likely caused by the use of setTimeout in your scrollBottom() function. setTimeout is asynchronous, meaning that it will not execute immediately, which means that your scrollBy() calls may be overwriting each other and not resulting in the desired effect.

To avoid this issue, you should use requestAnimationFrame() instead of setTimeout. This will ensure that the scrollBy() calls are executed in the same frame, and should result in the desired autoscrolling behavior.

Try this instead:

const scrollBottom: () => void | NodeJS.Timeout = useCallback(() => {
 window.scrollBy(0, 10);

 if (!overBottomTrigger.current) return;

 if (document.body.scrollHeight - window.innerHeight - window.scrollY < 20) return;

 requestAnimationFrame(scrollBottom);
}, []);

That's how I recreate scrollBottom function to make it work as expected:

  const scrollBottom: () => void = useCallback(() => {
   if (!overBottomTrigger.current) return;
   if (document.body.scrollHeight - window.innerHeight - window.scrollY <= 0) 
    return;

   window.scrollBy(0, 1);
   window.onscroll = () => {
    scrollBottom();
   };
 }, []);

I understand, that listeners creates macrotasks, as setTimeout, but it's still unclear why the previous variant doesn't work. Does scrollBy function accumulates their executions, to execute it once?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM