From 85aa0249b96cd73cc26cc6bf24441419990318dd Mon Sep 17 00:00:00 2001 From: seaislee1209 Date: Sat, 25 Apr 2026 14:17:01 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20v0.19.5=20=E7=94=9F=E6=88=90=E9=A1=B5?= =?UTF-8?q?=E6=85=A2=E9=80=9F=E5=BE=80=E4=B8=8A=E6=BB=9A=E4=BB=8D=E8=B7=B3?= =?UTF-8?q?=E5=88=B0=E5=BA=95=E9=83=A8=20=E2=80=94=20=E5=8F=8C=E5=B1=82=20?= =?UTF-8?q?anchor=20=E5=8F=A0=E5=8A=A0=E6=A0=B9=E5=9B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v0.19.4 只修了 useEffect 的 scrollToBottom 误触, 没修 handleScroll 里的 anchor 累加。用户实测: 鼠标滚轮慢速 / 慢拖滑动条往上翻仍会 跳到最底部, 但快速拽到顶不复现。 根因(双层 anchor 叠加) 1. 浏览器自动 scroll anchoring(默认 overflow-anchor: auto): 滚动 容器头部插入内容时浏览器自动 scrollTop += diff, 保持视觉位置 2. handleScroll 里 .then 又手动 el.scrollTop += diff => 双倍 anchor, 总位移 = 2 * diff, 把 scrollTop 推到 max(底部) 为什么"快速拽不复现, 慢速复现": 浏览器 scroll anchoring 在用户主动 scroll 期间会暂时关闭。快速 拽到顶后立即 loadMore 完成, 浏览器把"用户刚释放"视作仍在 scroll 跳过自动 anchor, 只手动一次, 不叠加。慢速操作时 scroll event 间 有 100~300ms 静止间隔, 浏览器认为 scroll 已停, 自动 anchor 启动 + 我们手动 anchor = 双倍。 修复(两道防线) 1. CSS: .contentArea 加 overflow-anchor: none, 彻底关掉浏览器自动 anchor, 由代码统一管 — 这是根因修复 2. handleScroll: 加 loadMoreInFlightRef 防重入 flag, 慢速操作下 多次进入 if 分支只 schedule 一次 anchor; rAF 完成后清 flag — 兜底防御, 避免极端时序下 rAF 累加 Co-Authored-By: Claude Opus 4.7 (1M context) --- web/src/components/VideoGenerationPage.module.css | 4 ++++ web/src/components/VideoGenerationPage.tsx | 14 +++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/web/src/components/VideoGenerationPage.module.css b/web/src/components/VideoGenerationPage.module.css index 407a6cb..d72f20f 100644 --- a/web/src/components/VideoGenerationPage.module.css +++ b/web/src/components/VideoGenerationPage.module.css @@ -17,6 +17,10 @@ flex: 1; overflow-y: auto; overflow-x: hidden; + /* 关掉浏览器自动 scroll anchoring:往上加载历史时由 handleScroll 里的 + anchor 逻辑统一管,避免浏览器默认的 anchor 与我们手动 +diff 叠加, + 导致慢速滚动 / 慢拖滑动条时页面被推到最底部。 */ + overflow-anchor: none; } .emptyArea { diff --git a/web/src/components/VideoGenerationPage.tsx b/web/src/components/VideoGenerationPage.tsx index 26f6b24..1fd0cd3 100644 --- a/web/src/components/VideoGenerationPage.tsx +++ b/web/src/components/VideoGenerationPage.tsx @@ -22,6 +22,9 @@ export function VideoGenerationPage() { const scrollRef = useRef(null); const prevLastIdRef = useRef(null); const initialLoadRef = useRef(true); + // 防重入 flag:loadMore + anchor 期间,handleScroll 多次触发不再 schedule 多个 rAF, + // 避免 anchor 累加把页面推到底(慢速滚轮 / 慢拖滑动条场景)。 + const loadMoreInFlightRef = useRef(false); const savedScrollTop = useGenerationStore((s) => s.savedScrollTop); const saveScrollPosition = useGenerationStore((s) => s.saveScrollPosition); const [detailTaskId, setDetailTaskId] = useState(null); @@ -76,15 +79,20 @@ export function VideoGenerationPage() { const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight; setShowScrollBottom(distanceFromBottom > 300); - // Trigger loadMore when scrolled within 100px of the top - if (scrollRef.current.scrollTop < 100) { + // Trigger loadMore when scrolled within 100px of the top. + // ref flag 守卫:只有第一次进入分支时才 schedule loadMore + anchor; + // 后续 handleScroll(慢速操作下持续触发)直接跳过,避免多次 rAF 排队累加 +diff。 + if (scrollRef.current.scrollTop < 100 && !loadMoreInFlightRef.current) { + loadMoreInFlightRef.current = true; const el = scrollRef.current; const prevHeight = el.scrollHeight; loadMore().then(() => { - // After older tasks are prepended, restore visual position so user doesn't jump + // After older tasks are prepended, restore visual position so user doesn't jump. + // CSS overflow-anchor: none 已禁用浏览器自动 anchor,由这里独立完成。 requestAnimationFrame(() => { const diff = el.scrollHeight - prevHeight; if (diff > 0) el.scrollTop += diff; + loadMoreInFlightRef.current = false; }); }); }