ScrollView maintainVisibleContentPosition applies the anchor delta to the already-clamped offset when content shrinks (iOS + Android, Fabric)
Chưa có ai nhận issue này.
- Ngôn ngữ chính
- C++
- Star
- 127k
- Fork
- 25.3k
- Merge trung bình
- 1 ngày 23 giờ
- Pull request đã merge (30 ngày)
- 4
Mô tả
Description
When a mount makes a ScrollView's content shorter such that the current contentOffset.y is past the new maximum scroll, both platforms clamp the offset during the mount and then add the anchor's delta to the already-clamped value. The anchor child is not maintained: the offset is driven below zero and clamps to 0, landing at the very top of the list.
The grow direction, using the same anchor and the same prop, is exact to the pixel on both platforms — which is what isolates the clamp ordering as the cause rather than the anchor measurement.
Android — MaintainVisibleScrollPositionHelper.updateScrollPositionInternal() reads scrollView.scrollY fresh after didMountItems, but ReactScrollView.onLayoutChange has already run in the same mount and done if (currentScrollY > maxScrollY) scrollTo(scrollX, maxScrollY).
iOS — RCTScrollViewComponentView._adjustForMaintainVisibleContentPosition() uses _scrollView.contentOffset.y + deltaY after updateState: has set the smaller contentSize, and UIKit clamps contentOffset the moment contentSize drops below it.
Both should apply the delta to the offset captured in the prepare step and clamp afterwards, not before. Both sites are unchanged on main as of filing.
Adjacent, possibly the same root cause: #52757 (maintainVisibleContentPosition scrolls when an item is removed from the head).
Steps to reproduce
Reproducer: https://github.com/muhasabahhub/rn-mvcp-shrink
A single ScrollView, 60 direct View children, no dependencies beyond expo/react/react-native. The on-screen header prints contentOffset.y, contentSize.height, the minIndexForVisible in effect, and the child currently at the top, with the pre-press values frozen beside the live ones.
SHRINK (the defect)
- Open the app — children start expanded (each carries an 8-line block).
- Scroll so child 44 is the first child under the header.
- Press Toggle once (each child loses its 8-line block).
- Observed: contentOffset.y is 0.0 and the child at top is 0 — about 45 children above the one the anchor named.
GROW (the control)
- Relaunch. Press Toggle once so the children are collapsed.
- Scroll so child 44 is the first child under the header.
- Press Toggle once (each child regains its 8-line block).
- Observed: child 44 is still at the top, exact.
Full measured numbers, both platforms, are in the repo's README.
Expected
The child at minIndexForVisible keeps its screen position when the content shrinks, as it does when it grows. Where the correct offset is reachable within the new content, the anchor should be maintained; only a target beyond the new maximum should clamp.
Actual
contentOffset.y is driven below zero and clamps to 0.0 — the list lands at the very top, about 45 children above the one the anchor named.
React Native Version
0.86.3
Affected Platforms
Runtime - Android, Runtime - iOS
Output of npx @react-native-community/cli info
OS: Windows 11 10.0.26200
Node: 24.13.1
npm: 11.19.0
expo: 57.0.23 (SDK 57, managed workflow)
react: 19.2.3
react-native: 0.86.3
Architecture: Fabric (new architecture — Expo SDK 57 default)
Devices:
- Samsung Galaxy S25, Android <fill in>, Expo Go
- iPhone 15, iOS 26.6.1, Expo Go
(react-native info is unavailable in this project — @react-native-community/cli is not a dependency of the bare repro. The above is from expo-env-info plus device details.)
Stacktrace or Logs
No crash or error; incorrect scroll position only.
MANDATORY Reproducer
https://github.com/muhasabahhub/rn-mvcp-shrink
Screenshots and Videos
Samsung S25:
Iphone 15:
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Hướng nghiên cứu
Bắt đầu với reproducer rn-mvcp-shrink được liên kết và kiểm tra Android’s MaintainVisibleScrollPositionHelper.updateScrollPositionInternal() cùng với iOS’s RCTScrollViewComponentView._adjustForMaintainVisibleContentPosition(). Tái hiện trường hợp shrink trên cả hai nền tảng và theo dõi chuỗi prepare, clamp và cập nhật anchor. Hoàn tất khi phần tử con được neo giữ nguyên vị trí trên màn hình trong trường hợp nội dung mới cho phép, còn các offset thực sự không thể truy cập vẫn được clamp.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- react-native
- Lĩnh vực
- mobile-dev
- Loại issue
- Lỗi
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức độ hoạt động
- Sôi nổi
- Độ rõ ràng
- Khá rõ ràng
- Mức phù hợp với người mới
- 68/100