petyosi / react-virtuoso

The most powerful virtual list component for React
https://virtuoso.dev
MIT License
5.13k stars 299 forks source link

add shouldDisableIntersectionObserverCallback #1098

Closed MartinDawson closed 2 months ago

MartinDawson commented 2 months ago

Why is this needed?

When you have a responsive grid that changes size and you have html5 video elements in that grid, when the user goes fullscreen it affects the size of the elements in the grid and causes a re-render due to the intersectionObserver (i debugged this a lot and it seems to just be how the fullscreen API works, i.e it affects the parent element of the fullscreen size).

When a user is going fullscreen mode you never want this to happen, you just want to disable this re-rendering from happening.

The solution is to provide a callback and disable this when in fullScreenMode.

Here's my implementation of it:

  const [currentFullscreenElement, setCurrentFullscreenElement] = useState(null)
  const previousFullscreenElement = useRef(null)

  useEffect(() => {
    const handleFullscreenChange = () => {
      previousFullscreenElement.current = currentFullscreenElement

      setCurrentFullscreenElement(document.fullscreenElement)

      if (document.fullscreenElement === null) {
        // kind of hacky to use setTimeout but it's needed to allow enough time for the intersectionObserver stuff to finish and then reset the previous element when going out of fullscreen
        setTimeout(() => {
          previousFullscreenElement.current = document.fullscreenElement
        }, 500)
      }
    }

    document.addEventListener('fullscreenchange', handleFullscreenChange)

    return () => {
      document.removeEventListener('fullscreenchange', handleFullscreenChange)
    }
  }, [currentFullscreenElement])

 const shouldDisableIntersectionObserverCallback = useCallback(() => {
    const disable =
      document.fullscreenElement !== null ||
      previousFullscreenElement.current !== null

    return disable
  }, [])

 <VirtuosoGrid
      shouldDisableIntersectionObserverCallback={
        shouldDisableIntersectionObserverCallback
      }
    />
petyosi commented 2 months ago

I've seen some scenarios where the recalculation should be suppressed. However, the callback approach is quite noisy. You need to have a state + useCallback. I believe that a method-based API like this would work better.

ref.current.suppressResizeObserve(true); // stops the observation
ref.current.suppressResizeObserve(false); // resumes it

Notice that the component does not use intersection observers, but resize ones.

MartinDawson commented 2 months ago

I've seen some scenarios where the recalculation should be suppressed. However, the callback approach is quite noisy. You need to have a state + useCallback. I believe that a method-based API like this would work better.

ref.current.suppressResizeObserve(true); // stops the observation
ref.current.suppressResizeObserve(false); // resumes it

Notice that the component does not use intersection observers, but resize ones.

Thanks, I'll see if I can make that change and update it at a later date. As this does my job for now.