Skip to content

[建议] 跳转时冻结悬浮窗,避免平滑滚动过程中的抖动/乱跳;点击即高亮,允许中断 #1

Description

@YuYangOUC

建议:跳转时冻结悬浮窗,避免平滑滚动过程中的抖动 / 乱跳

先说明:以下改动我在本机 0.3.1 的 lib/client.js 上已实测有效(本地补丁),提出来是想请作者评估是否值得合入官方版本。

问题

点击悬浮窗条目跳转时:

  1. 滚动过程中悬浮窗会跟着乱跳:scrollIntoView({behavior:"smooth"}) 平滑滚动期间持续触发 scroll 事件 → updateActive() 每滚过一条消息就更新一次高亮 → 高亮跟随效果(useEffect([activeIndex]) 里的 el.scrollTo)反复滚动悬浮窗,肉眼看到"乱跳"。
  2. 点击后不能立即得到反馈:高亮要等页面滚完才变。
  3. 滚动进行中点击其它条目,多个平滑动画互相抢占 + 旧的兜底逻辑会提前解除冻结,进一步放大抖动。

期望行为

  • 点击条目 → 立即变蓝(不等滚动结束)
  • 页面平滑滚动(保留动画),且滚动中允许被新点击中断(新目标直接接管)
  • 但从点击到滚动结束,悬浮窗完全静止:不追高亮、不自我滚动、不居中
  • 滚动结束后,按阅读位置校正一次高亮

实现思路(改动都在 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions