virtual-core: desktop Safari drops the prepend anchor write when history lands during the top rubber-band bounce
还没有人认领这个 Issue。
评估
- 难度
- 5/5
- 预计耗时
- 一周以上
- 新手友好度
- 38/100
- Issue 类型
- 缺陷
- 描述清晰度
- 基本清楚
- 活跃度
- 活跃
- 技术栈
- react, typescript
- 领域
- frontend, performance
调研方向
在桌面版 Safari 中使用 pnpm --filter tanstack-react-virtual-example-chat dev 复现,并在顶部橡皮筋回弹期间执行快速 prepend。阅读 setOptions、_willUpdate 和 _flushIosDeferredIfReady,将桌面写入路径与 iOS overscroll 处理以及 #1189 和 #1280 中引用的技术进行比较。完成标准是:以末尾为锚点的列表在回弹期间保持读者所在的行,同时不会渲染空白 viewport。
由索引模型根据 Issue 内容生成。
描述
Describe the bug
On macOS Safari (18.4, navigator.maxTouchPoints 0, so none of the iOS deferral is involved), an end-anchored list loses its reading position when a prepend lands while the scroller is rubber-banding past its top edge.
Sequence, from a per-frame trace of the react chat example (anchorTo: 'end', directDomUpdates: true, 180 ms mock history load):
1283ms st=159 off=159 range=1-10 count=45 top=1
1300ms st=0 off=0 range=0-9 count=45 top=0 <- auto history load triggers
1316ms st=-20 off=-20 range=0-8 count=45 top=0 <- Safari rubber band, scrollTop negative
1383ms st=-69 off=-69 range=0-8 count=45 top=0
1466ms st=-51 off=-51 range=0-8 count=45 top=0
1484ms st=-46 off=-46 range=0-10 count=57 top=-12 <- 12 rows prepended, anchor write lost
setOptions resolves the anchor and _willUpdate writes scrollTop = 842 to keep row 0 in place. WebKit discards a scrollTop write made during an elastic bounce and animates back to 0. The next bounce scroll event reports the DOM value, the tracked offset follows it, and the viewport now shows the newly prepended rows: the reader is exactly one prepend away from where they were. While the eagerly bumped offset and the DOM disagree the viewport can also render blank, since the range is computed for a position the DOM never reaches.
The iOS flush path already treats the overscroll zone as unwritable (_flushIosDeferredIfReady, "Phase 2b"). The desktop write path has no such guard.
Steps to reproduce
pnpm --filter tanstack-react-virtual-example-chat dev, open in desktop Safari with a trackpad.- Fling upward hard enough to hit the top and bounce.
- The 180 ms mock load lands during the bounce. The list shows the newly loaded rows instead of the row you were reading.
With a slower (real) backend the load usually lands after the bounce has settled and the write goes through, so this needs a fast prepend to trigger. Cached or prefetched history makes that common.
What was tried
A deferral of writes made while scrollOffset < 0, replayed on the first in-bounds scroll event, lands on the correct row but shows the new rows for the duration of the bounce and then snaps back. That reads as a glitch every time and was dropped from #1280. On WebKit the reader's row cannot be held still through the bounce with scrollTop at all.
Options
- App level, to verify first:
overscroll-behavior: noneon the scroll container. Safari 16+ supports it and per specnonesuppresses the boundary bounce. If it removes the rubber band, the failure window disappears and the chat docs can recommend it next tooverflow-anchor: none. - Core: a CSS offset on the container for the bounce window, swapped for a real
scrollTopwrite once the offset is back in bounds. This is the technique from #1189, scoped to this one case. - Example: the auto-load fires on every bounce scroll event below 120 px, so after the first load finishes the bounce triggers a second one. Independent of the core issue, but it amplifies it.
Platform
- macOS, Safari 18.4 (20621.1.15.11.10)
@tanstack/virtual-coreatmain(also reproduces with #1280 applied; unrelated to the iOS path)
Related: #1280, #1189, #884.
- 主要语言
- TypeScript
- 星标
- 7.1k
- 派生
- 466
- 平均合并
- 2 天 31 分钟
- 30 天内合并 PR
- 13
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
TanStack/virtual 的其他 Issue
-
难度 4/5 3-5 天 新手友好度 55/100
-
难度 4/5 3-5 天 新手友好度 62/100
-
难度 4/5 3-5 天 新手友好度 55/100
-
难度 3/5 1-2 天 新手友好度 68/100
-
难度 4/5 3-5 天 新手友好度 42/100
相似的 Issue
-
VerificationGate: ATTRIBUTION quote guard never matches a normal quotation (\b around the quote) 未关闭
难度 2/5 1-3 小时 新手友好度 75/100
danielmiessler/LifeOS#2234 ·
-
T: Bug
难度 2/5 1-3 小时 新手友好度 75/100
-
难度 2/5 1-3 小时 新手友好度 65/100
-
难度 1/5 1 小时以内 新手友好度 85/100
-
Mend: dependency security vulnerability untriaged
难度 2/5 1-3 小时 新手友好度 70/100