Implement maintainVisibleContentPosition #2588
Open
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This implements the
maintainVisibleContentPosition
prop onScrollView
(https://reactnative.dev/docs/scrollview#maintainvisiblecontentposition), as well as normalizing the scrolling behavior when adding items at the top of a ScrollView.Currently the behavior varies depending on the browser. In Chrome the content position is maintained if the scroll position is > 0, while on Safari it is never maintained. The Chrome behavior diverges from react-native so this also makes sure it is consistent, which is that by default the content position is not maintained, and that when the
maintainVisibleContentPosition
prop is set it is.The general logic is adapted from the iOS implementation in react-native (https://github.com/facebook/react-native/blob/main/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm#L723-L787). The general idea is to get the position of the first visible view before changes to the view hierarchy, compare it to its position after the changes and adjust the scroll position accordingly. In react-native there are UIManager hooks that are executed before / after a transaction is committed. On the web we can use
MutationObserver
to detect changes to the subtree of theScrollView
, but sadly I don't think there are any way to have a callback executed before the changes are committed so instead we update the first visible view on scroll events.To fix the Chrome behavior that automatically preserves the scroll position we save the last scroll position from scroll events and reset it to that last value in the
MutationObserver
callback. This successfully reverts the adjustment Chrome did.I also updated the example app to include these new features in the
ScrollView
example.Here's a few videos of the example before and after the changes
Before
Chrome maintains scroll position when not scrolled at the top, Safari doesn't.
Chrome:
Screen.Recording.2023-10-04.at.19.48.43.mov
Safari:
Screen.Recording.2023-10-04.at.19.57.24.mov
After
Same behavior in Chrome and Safari, shows without mvcp, then with mvcp, then with autoscrollToTopThreshold.
Chrome:
Screen.Recording.2023-10-04.at.20.03.00.mov
Safari:
Screen.Recording.2023-10-04.at.20.01.04.mov