建议:跳转时冻结悬浮窗,避免平滑滚动过程中的抖动 / 乱跳
先说明:以下改动我在本机 0.3.1 的 lib/client.js 上已实测有效(本地补丁),提出来是想请作者评估是否值得合入官方版本。
问题
点击悬浮窗条目跳转时:
- 滚动过程中悬浮窗会跟着乱跳:
scrollIntoView({behavior:"smooth"}) 平滑滚动期间持续触发 scroll 事件 → updateActive() 每滚过一条消息就更新一次高亮 → 高亮跟随效果(useEffect([activeIndex]) 里的 el.scrollTo)反复滚动悬浮窗,肉眼看到"乱跳"。
- 点击后不能立即得到反馈:高亮要等页面滚完才变。
- 滚动进行中点击其它条目,多个平滑动画互相抢占 + 旧的兜底逻辑会提前解除冻结,进一步放大抖动。
期望行为
- 点击条目 → 立即变蓝(不等滚动结束)
- 页面平滑滚动(保留动画),且滚动中允许被新点击中断(新目标直接接管)
- 但从点击到滚动结束,悬浮窗完全静止:不追高亮、不自我滚动、不居中
- 滚动结束后,按阅读位置校正一次高亮
实现思路(改动都在 TimelineRail 内)
1. 跳转冻结标志(ref,不触发重渲染):
const jumpPendingRef = useRef(false);
const jumpFallbackRef = useRef(null);
2. updateActive() 冻结守卫——冻结期间 scroll-spy 不更新高亮:
const updateActive = () => {
if (jumpPendingRef.current) return;
// ...原逻辑
};
3. 滚动静止检测——平滑滚动会连续触发 scroll 事件,用"最后一次事件后 150ms 无新事件"判定滚动结束,解除冻结并刷新一次高亮;只在冻结期间武装这个定时器(平时手动滚动不产生额外定时器开销):
let scrollTimer = null;
let settleTimer = null;
const onScroll = () => {
if (jumpPendingRef.current) {
if (settleTimer !== null) clearTimeout(settleTimer);
settleTimer = setTimeout(() => {
settleTimer = null;
jumpPendingRef.current = false;
updateActive();
}, 150);
}
if (scrollTimer !== null) return;
scrollTimer = setTimeout(() => { scrollTimer = null; updateActive(); }, 60);
};
4. 高亮跟随效果加冻结守卫——点击只变蓝,悬浮窗不滚动居中:
useEffect(() => {
if (jumpPendingRef.current) return; // 冻结期间不跟随
if (activeIndex < 0) return;
// ...原 scrollTo 逻辑
}, [activeIndex]);
5. onClick:点击即高亮 + 置位冻结 + 兜底定时器(允许中断:新点击直接接管,旧兜底定时器先清掉再挂新的,防止旧定时器在新动画中途提前解除冻结;1.5s 兜底覆盖 reduced-motion 下瞬时跳转不产生 scroll 事件的场景):
onClick: () => {
if (key === void 0 || jumping) return;
jumpAbortRef.current?.abort();
const controller = new AbortController();
jumpAbortRef.current = controller;
setJumping(true);
setActiveIndex(i); // 立即变蓝
jumpPendingRef.current = true;
if (jumpFallbackRef.current !== null) window.clearTimeout(jumpFallbackRef.current);
jumpFallbackRef.current = window.setTimeout(() => {
jumpFallbackRef.current = null;
jumpPendingRef.current = false;
}, 1500);
jumpToMessage(sessionsService, sessionId, key, void 0, controller.signal).finally(() => setJumping(false));
},
6. 卸载时清理兜底定时器:
useEffect(() => () => {
jumpAbortRef.current?.abort();
if (jumpFallbackRef.current !== null) window.clearTimeout(jumpFallbackRef.current);
}, []);
附注
- 以上只改了编译产物
lib/client.js,源文件 src/ 里对应逻辑位置相同,合入时按同样思路改源码即可。
- 已知取舍:冻结期间悬浮窗不自动滚到目标条目,因此点击超出可视区的条目时看不到变蓝(条目在视野外);如需"点远条目自动滚过去",可考虑在滚动结束后由 settle 回调补一次
scrollTo。
- 150ms 静默阈值与 1.5s 兜底时长可按实际手感调整。
环境
- 插件:dsh-chat-rail 0.3.1(npm 最新)
- 平台:macOS / Chrome
建议:跳转时冻结悬浮窗,避免平滑滚动过程中的抖动 / 乱跳
先说明:以下改动我在本机 0.3.1 的
lib/client.js上已实测有效(本地补丁),提出来是想请作者评估是否值得合入官方版本。问题
点击悬浮窗条目跳转时:
scrollIntoView({behavior:"smooth"})平滑滚动期间持续触发scroll事件 →updateActive()每滚过一条消息就更新一次高亮 → 高亮跟随效果(useEffect([activeIndex])里的el.scrollTo)反复滚动悬浮窗,肉眼看到"乱跳"。期望行为
实现思路(改动都在
TimelineRail内)1. 跳转冻结标志(ref,不触发重渲染):
2.
updateActive()冻结守卫——冻结期间 scroll-spy 不更新高亮:3. 滚动静止检测——平滑滚动会连续触发 scroll 事件,用"最后一次事件后 150ms 无新事件"判定滚动结束,解除冻结并刷新一次高亮;只在冻结期间武装这个定时器(平时手动滚动不产生额外定时器开销):
4. 高亮跟随效果加冻结守卫——点击只变蓝,悬浮窗不滚动居中:
5. onClick:点击即高亮 + 置位冻结 + 兜底定时器(允许中断:新点击直接接管,旧兜底定时器先清掉再挂新的,防止旧定时器在新动画中途提前解除冻结;1.5s 兜底覆盖 reduced-motion 下瞬时跳转不产生 scroll 事件的场景):
6. 卸载时清理兜底定时器:
附注
lib/client.js,源文件src/里对应逻辑位置相同,合入时按同样思路改源码即可。scrollTo。环境