Skip to content
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

Window & Dynamic Size List Virtualization Causes Mobile Safari IOS Scroll Momentum Interruption #884

Open
2 tasks done
kevin-sjj opened this issue Nov 22, 2024 · 1 comment

Comments

@kevin-sjj
Copy link

kevin-sjj commented Nov 22, 2024

Describe the bug

By using the useWindowVirtualizer hook to apply virtualization for list items with different heights in relation to window scrolling, when you scroll, the virtualization behavior will occur.

Items that no longer need to be rendered will disappear from the DOM, and the list items corresponding to the visible indices will begin to appear in the DOM.

On Mobile iOS Safari, momentum scrolling triggered by user gestures works, but when combined with virtualization, the momentum effect fails to end smoothly and instead stutters or breaks abruptly. Is there a way to resolve this issue?

import "./styles.css";
import { useWindowVirtualizer } from "@tanstack/react-virtual";

const createRandomHeights = (
  min: number,
  max: number,
  count: number = 1000
) => {
  const heights = [];
  for (let i = 0; i < count; i++) {
    heights.push(Math.floor(Math.random() * (max - min + 1) + min));
  }
  return heights;
};

const randomHeights = createRandomHeights(100, 400);
const colors = ["red", "yellow", "blue"];

export default function App() {
  const virtualizer = useWindowVirtualizer({
    count: 1000,
    estimateSize: () => 400,
    overscan: 5,
    scrollMargin: 0,
  });

  const virtualItems = virtualizer.getVirtualItems();

  return (
    <div>
      <div
        style={{
          height: `${virtualizer.getTotalSize()}px`,
          position: "relative",
          width: "100%",
        }}
      >
        <div
          style={{
            transform: `translateY(${
              (virtualItems[0]?.start ?? 0) - virtualizer.options.scrollMargin
            }px)`,
            position: "absolute",
            left: "0",
            top: "0",
            width: "100%",
          }}
        >
          {virtualItems.map((virtualItem) => {
            return (
              <div
                key={virtualItem.key}
                data-index={virtualItem.index}
                ref={virtualizer.measureElement}
                style={{
                  paddingTop: "40px",
                }}
              >
                <div
                  style={{
                    height: randomHeights[virtualItem.index],
                    backgroundColor: colors[virtualItem.index % 3],
                  }}
                ></div>
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}

Your minimal, reproducible example

https://codesandbox.io/p/sandbox/qhkvyz

Steps to reproduce

  1. Go to CodeSandbox Link
  2. Scroll down Preview Page
  3. As you scroll, the momentum scroll stops occurring and the scrolling becomes increasingly choppy.

Expected behavior

I hope to maintain a seamless momentum scroll experience.

How often does this bug happen?

Every time

Screenshots or Videos

KakaoTalk_Video_2024-11-22-11-28-21.mp4
2024-11-22.11.58.26.mov

Platform

  • OS: IOS
  • Browser: Safari
  • Version: 18.1, 17.6.1

tanstack-virtual version

v3.10.6

TypeScript version

v5.4.5

Additional context

It seems that the CodeSandbox link is difficult to run on Mobile iOS Safari at the moment. However, you can reproduce the issue by running the attached source code.

Terms & Code of Conduct

  • I agree to follow this project's Code of Conduct
  • I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.
@tustvold
Copy link

I'm no expert here, so take my findings with a pinch of salt

When an element is first "mounted" the size is measured, if the element is a different size from expected it will adjust the scroll position to hide this. Safari appears to only preserve a momentum scroll when doing this if it is the same direction, e.g. if currently scrolling down and I set the scroll to an earlier position, it will cancel it.

The result is that if scrolling down when seeing an element for the first time, the estimated size needs to be larger than elements actually are. This is called out in the docs - https://tanstack.com/virtual/latest/docs/api/virtualizer#estimatesize

If you are dynamically measuring your elements, it's recommended to estimate the largest possible size (width/height, within comfort) of your items. This will ensure features like smooth-scrolling will have a better chance at working correctly

The key for me was that I was implementing a message view which scrolls up by default, this flips the logic round and instead the estimate needs to be smaller than possible. Then when the user scrolls back down it will have already measured the elements and everything will work "correctly".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants