Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

bug: MessageList restoreAnchor oscillates on iOS WebKit after loading older messages, leaving the list at an arbitrary scroll position

Đã đóng
#3,311 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
3/5
Thời gian dự kiến
1-2 ngày
Mức phù hợp với người mới
73/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
react, typescript
Lĩnh vực
frontend

Hướng nghiên cứu

Start in the TypeScript source for useScrollLocationLogic (src/components/MessageList/hooks/), at restoreAnchor/applyAnchor, plus the preserve-anchor entry point in useMessageListScrollManager. Replace the per-frame relative scrollBy correction with an absolute listElement.scrollTop derived from the anchor's offset within scroll content, so re-applying it cannot compound. Look for existing tests of these scroll hooks, add coverage showing repeated application converges instead of oscillating, and check whether the 1200 ms settle timeout and sign-flip guard still behave as described.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

Describe the bug

On iOS (Safari, and Chrome iOS, which also runs on WebKit), the non-virtualised MessageList moves the user away from the message they were reading every time an older page of messages loads. They usually end up near the newest messages. Our users describe it as "can't scroll up through the conversation, it keeps jumping back".

The cause is the older-page anchor restore in useScrollLocationLogic. On iOS WebKit its per-frame relative scrollBy correction never settles. It flips the list between two scroll positions until the 1200 ms settle timeout stops it, and the list stays wherever it is at that moment.

To Reproduce

  1. On an iOS device or simulator, open a channel with more than 25 messages, so there is more than one page.
  2. Scroll up with touch until older messages start loading. At that point the list's scrollTop is between 1 and 250, so useMessageListScrollManager takes the preserve-anchor path.
  3. When the older page is prepended, the view jumps away from the message you were reading, often by more than a screen height towards the bottom.
  4. Scroll up again. The next page load jumps again.

We logged the calls by patching Element.prototype.scrollBy, scrollTo and the scrollTop setter on .str-chat__message-list, with stack traces. This is one older-page load on the iOS 26.3 simulator:

t(ms)  caller                          arg              scrollTop before call
8904   restoreAnchor.applyAnchor       scrollBy +1983   222
8948   restoreAnchor.queueNextFrame    scrollBy -1360   3565
8958   restoreAnchor.queueNextFrame    scrollBy +1360   845
9041   restoreAnchor.queueNextFrame    scrollBy -780    3565
9052   restoreAnchor.queueNextFrame    scrollBy +780    2005
9072   restoreAnchor.queueNextFrame    scrollBy -780    3565
...    (~70 calls, alternating -780 / +780, scrollTop flipping 2005 <-> 3565)
10088  restoreAnchor.queueNextFrame    scrollBy +780    2005
       -> 1200 ms timeout; list left at scrollTop 3565 (scrollHeight 4526, clientHeight 381)
  • The user was at scrollTop ≈ 218 before the load.
  • Every later older-page load repeats the pattern. We saw bursts of 60–68 calls with deltas of ±1452, ±254, and mixed values up to about 4900.
  • Each scrollBy moves the list by about twice the requested delta, so the next frame measures the same error with the opposite sign.

That fits the geometry read in the frame after a scrollBy still reflecting the pre-scroll position on iOS. With a relative correction, the loop flips between two positions instead of settling.

Where (14.12.1, dist/es/components/MessageList/hooks/MessageList/):

  • useScrollLocationLogic.mjs, restoreAnchor (≈ lines 65–121). applyAnchor computes offsetDelta from getBoundingClientRect and calls listElement.scrollBy({ top: offsetDelta }) (≈ line 82) on every animation frame. It stops after 2 stable frames, or at setTimeout(cleanup, 1200).
  • useMessageListScrollManager.mjs: it is entered from restoreAnchor(preservedAnchor) (≈ line 84) in preserve-anchor mode.

Expected behavior

After older messages are prepended, the message the user was reading stays where it was.

Suggested fix

Make the restore idempotent:

  1. Compute the anchor's position without depending on the current scroll. For example, use the anchor element's offsetTop relative to the list's scroll content instead of getBoundingClientRect.
  2. Assign an absolute listElement.scrollTop = anchorContentTop - anchor.offsetTop instead of a relative scrollBy.

Re-applying that on later frames can't compound, whatever the engine's read-after-write timing. A cheaper guard is to stop the loop as soon as offsetDelta changes sign, but that only limits the damage.

What we tried as integrators:

  • No public prop turns off the anchor path; disableScrollManagement is internal.
  • overflow-anchor: none on the list has no effect.
  • Keeping pagination in stick-to-top mode avoids the loop, but drops the reading position by a whole page.

Screenshots

n/a. The scroll log above shows the behaviour; we can share the full logs and the logging snippet.

Package version

  • stream-chat-react: 14.12.1. The scroll hooks are byte-identical from 14.9.0 to 14.12.2, so latest is affected.
  • stream-chat-css: bundled with stream-chat-react 14.12.1
  • stream-chat-js: 9.53.0

Desktop (please complete the following information):

Not affected:

  • Chrome (Playwright Chromium): native scroll anchoring keeps the anchor in place, so restoreAnchor makes zero corrections.
  • Desktop WebKit 26.6 (Playwright): one scrollBy, then it settles.

Smartphone (please complete the following information):

  • Device: iPhone 17 Pro simulator (logged above); iPhone 15 Pro simulator (visual repro); a physical iPhone (field report)
  • OS: iOS 26.3 and iOS 17.5 (simulators); the physical iPhone's OS version is unknown
  • Browser: Safari (simulators); Chrome iOS 154 (field report)

Additional context

The list is the non-virtualised <MessageList /> with default props, inside Channel / Window, with messages paginated 25 at a time.

Ngôn ngữ chính
TypeScript
Star
845
Fork
297
Merge trung bình
18 giờ 42 phút
Pull request đã merge (30 ngày)
23

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. 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.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của GetStream/stream-chat-react

Tất cả issue của GetStream/stream-chat-react

Issue tương tự

Thêm issue về TypeScript

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.