SoFunction
Updated on 2025-04-07

react implementation prevents parent container from scrolling

react prevents parent container from scrolling

Recently, a problem occurred when I was doing code migration. I found that a custom scrollbar component I wrote before had a bug, and the parent container would also scroll when scrolling.

Look at the code, the code has been simplified

export default ()=>{
return return (
    <div
      className={classNames(getCls('container'),  ? 'active' : '', className)}
      ref={scrollDOMRef}
      onWheelCapture={(e: any) => {
        ();
        if ( < 0) {
          // Upward          setDragY(dragY - dragSpeed);
        } else {
          // Down          setDragY(dragY + dragSpeed);
        }
      }}>
    </div>
  );
}

Since the parent container will scroll, just block the default behavior();, but useless.

Here I guess that because react events are synthetic events, all events are registered on the document, so the default behavior that causes blocking is not blocked to the parent container.

Then use the native one.

useEffect(() => {
    if () {
      ('wheel', (e) => {
        ();
        if ( < 0) {
          // Upward          setDragY(dragY - dragSpeed);
        } else {
          // Down          setDragY(dragY + dragSpeed);
        }
      });
    }
  }, []);

When using native events and react events together, be careful and consider preventing bubbles, because it may cause react synthesis events to fail.

Then will it be fine after doing this?

really

The parent container is not scrolling

But I fell into the closure trap of react

The registration function was not updated in time, and the closures of dragY and dragSpeed ​​resulted in a bug.

So what should I do?

useEffect(() => {
    if () {
      ('wheel', (e) => {
        ();
        if ( < 0) {
          // Upward          setDragY(dragY - dragSpeed);
        } else {
          // Down          setDragY(dragY + dragSpeed);
        }
      });
    }
  }, [, dragY, dragSpeed]);

Another problem has emerged

Each time, the previous event should be destroyed and then registered.

Otherwise, multiple events will trigger at the same time and cause a bug.

useEffect(() => {
    const handle = (e: any) => {
      ();
      if ( < 0) {
        // Upward        setDragY(dragY - dragSpeed);
      } else {
        // Down        setDragY(dragY + dragSpeed);
      }
    };
    if () {
      ('wheel', handle, {
        passive: false,
      });
    }
    return () => {
      if () {
        ('wheel', handle);
      }
    };
  }, [, dragY, dragSpeed]);

passive

When passive is false, the browser only knows whether preventDefault has been called after executing the callback function. If preventDefault is not called, then the default behavior is executed, which is scrolling. This will cause the scrolling to be unsmooth.

Passive is true, which means telling the browser not to call preventDefault, and the browser just executes scrolling directly, without considering the callback function.

At this time, even if you call preventDefault in the callback function, it will not take effect.

mdn says that in some browsers (especially Chrome and Firefox), if you listen to window, document or touchstart and touchmove on it, the passive will be set to true by default.

I still need to remind everyone that when you don't need to call preventDefault, listen to scroll or touchmove and set passive to true

Summarize

The above is personal experience. I hope you can give you a reference and I hope you can support me more.