bug: MessageList restoreAnchor oscillates on iOS WebKit after loading older messages, leaving the list at an arbitrary scroll position
Maintainer antworten meist innerhalb von 1 Tag
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 3/5
- Geschätzter Aufwand
- 1-2 Tage
- Anfängerfreundlichkeit
- 73/100
- Issue-Typ
- Bug
- Klarheit
- Klar beschrieben
- Aktivitätsstatus
- Aktiv
- Tech-Stack
- react, typescript
- Bereich
- frontend
Rechercherichtung
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.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
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
- On an iOS device or simulator, open a channel with more than 25 messages, so there is more than one page.
- Scroll up with touch until older messages start loading. At that point the list's
scrollTopis between 1 and 250, souseMessageListScrollManagertakes thepreserve-anchorpath. - 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.
- 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
scrollBymoves 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).applyAnchorcomputesoffsetDeltafromgetBoundingClientRectand callslistElement.scrollBy({ top: offsetDelta })(≈ line 82) on every animation frame. It stops after 2 stable frames, or atsetTimeout(cleanup, 1200).useMessageListScrollManager.mjs: it is entered fromrestoreAnchor(preservedAnchor)(≈ line 84) inpreserve-anchormode.
Expected behavior
After older messages are prepended, the message the user was reading stays where it was.
Suggested fix
Make the restore idempotent:
- Compute the anchor's position without depending on the current scroll. For example, use the anchor element's
offsetToprelative to the list's scroll content instead ofgetBoundingClientRect. - Assign an absolute
listElement.scrollTop = anchorContentTop - anchor.offsetTopinstead of a relativescrollBy.
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;
disableScrollManagementis internal. overflow-anchor: noneon the list has no effect.- Keeping pagination in
stick-to-topmode 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
restoreAnchormakes 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.
- Vorherrschende Sprache
- TypeScript
- Sterne
- 845
- Forks
- 298
- Ø Merge
- 18 Std. 42 Min.
- Gemergte PRs (30 T.)
- 23
Entwicklungsumgebung
- Kein Dockerfile und keine Docker-Compose-Datei
- Hat eine Pull-Request-Vorlage
- Beitragsleitfaden lesen
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus GetStream/stream-chat-react
-
bug: attachment upload notifications are shown again in other channels after switching channelOffenbug released on @rc status:confirmed
Schwierigkeit 4/5 3-5 Tage Anfängerfreundlichkeit 52/100
GetStream/stream-chat-react#3279 · 4 Kommentare ·
Maintainer antworten meist innerhalb von 1 Tag
-
bug status:confirmed WIP
Schwierigkeit 3/5 1-2 Tage Anfängerfreundlichkeit 62/100
GetStream/stream-chat-react#3147 · 3 Kommentare ·
Maintainer antworten meist innerhalb von 1 Tag
-
bug status: unconfirmed
Schwierigkeit 3/5 1-2 Tage Anfängerfreundlichkeit 38/100
GetStream/stream-chat-react#2863 · 4 Kommentare ·
Maintainer antworten meist innerhalb von 1 Tag
-
bug status:confirmed
Schwierigkeit 3/5 1-2 Tage Anfängerfreundlichkeit 30/100
GetStream/stream-chat-react#2652 · 2 Kommentare ·
Maintainer antworten meist innerhalb von 1 Tag
-
bug status: unconfirmed
Schwierigkeit 4/5 3-5 Tage Anfängerfreundlichkeit 42/100
GetStream/stream-chat-react#2647 · 2 Kommentare ·
Maintainer antworten meist innerhalb von 1 Tag
Alle Issues in GetStream/stream-chat-react
Ähnliche Issues
-
community first-timers-only good first issue hacktoberfest help wanted low hanging fruit up-for-grabs
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 90/100
lingdojo/kana-dojo#32663 · 1 Kommentar · 5 Reaktionen ·
Maintainer antworten meist innerhalb von 1 Tag
-
Admin: Title Separator field can't be cleared (input falls back to "|")Evtl. vergeben @emdashbot hat das heute übernommen. Offenbot:bug bot:in-review
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 80/100
emdash-cms/emdash#4236 · 3 Kommentare ·
Maintainer antworten meist innerhalb von 1 Tag
-
PHP workflow streaming requests buffer the full SSE responseEvtl. vergeben @Winoren hat das heute übernommen. Offenreview: medium
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 84/100
langgenius/dify#44270 · 1 Kommentar · 1 Reaktion ·
Maintainer antworten meist innerhalb von 1 Tag
-
area:holodeck review-finding
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 62/100
hyperdrive-technology/holodeck#142 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 90/100
code-yeongyu/senpi#3573 ·
Maintainer antworten meist innerhalb von 1 Tag