From 2c6d208be7e98e0ed101c09dec8c396b7be1d06d Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Fri, 19 Jun 2026 21:24:47 +0800 Subject: [PATCH] =?UTF-8?q?fix(input):=20/=E8=8F=9C=E5=8D=95=E4=B8=8A?= =?UTF-8?q?=E4=B8=8B=E9=94=AE=E6=BB=9A=E5=8A=A8=E6=8A=96=E5=8A=A8=E4=B8=8E?= =?UTF-8?q?=E9=AB=98=E4=BA=AE=E6=9D=A1=E5=81=B6=E5=8F=91=E9=97=AA=E7=83=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 rAF 第一帧 now 早于 startTime 导致 scrollTop 反向抖动 - 过滤动画结束后的延迟 scroll 事件,避免误进入 manualScrolled 模式导致高亮条闪一下消失 --- static/src/components/input/InputComposer.vue | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/static/src/components/input/InputComposer.vue b/static/src/components/input/InputComposer.vue index 377adc0..3154f29 100644 --- a/static/src/components/input/InputComposer.vue +++ b/static/src/components/input/InputComposer.vue @@ -597,6 +597,11 @@ const slashHighlightTop = ref(4); /** 手动滚动后切到鼠标 hover 模式,隐藏 JS 高亮条 */ const slashManualScrolled = ref(false); let slashAnimId: number | null = null; +/** 动画真正进行中(比 animId 更可靠,可覆盖 rAF 结束后的延迟 scroll 事件) */ +let slashAnimating = false; +/** 记录最近一次动画的目标 scrollTop 和结束时间,用于过滤动画后的延迟 scroll 事件 */ +let lastSlashAnimatedScroll: number | null = null; +let lastSlashAnimationEndTime = 0; const slashHighlightStyle = computed(() => { if (slashManualScrolled.value) return { display: 'none' }; @@ -615,6 +620,8 @@ const animateSlash = (element: HTMLElement, targetScroll: number) => { if (interrupted) { cancelAnimationFrame(slashAnimId); } + slashAnimating = true; + lastSlashAnimatedScroll = targetScroll; const startScroll = element.scrollTop; const startHighlight = slashHighlightTop.value; const distScroll = targetScroll - startScroll; @@ -624,7 +631,8 @@ const animateSlash = (element: HTMLElement, targetScroll: number) => { const startTime = performance.now(); const step = (now: number) => { - const elapsed = now - startTime; + // rAF 第一帧的 now 偶尔会早于 startTime,导致 progress 为负、scrollTop 反向抖动 + const elapsed = Math.max(0, now - startTime); const progress = Math.min(elapsed / duration, 1); const eased = 1 - Math.pow(1 - progress, 3); element.scrollTop = startScroll + distScroll * eased; @@ -633,6 +641,8 @@ const animateSlash = (element: HTMLElement, targetScroll: number) => { slashAnimId = requestAnimationFrame(step); } else { slashAnimId = null; + slashAnimating = false; + lastSlashAnimationEndTime = performance.now(); } }; slashAnimId = requestAnimationFrame(step); @@ -640,7 +650,18 @@ const animateSlash = (element: HTMLElement, targetScroll: number) => { /** 手动滚动时切到鼠标 hover 模式,不再跟踪键盘选中 */ const onSlashScroll = () => { - if (slashAnimId !== null) return; + if (slashAnimating || slashAnimId !== null) return; + const list = skillSlashList.value; + if (!list) return; + const now = performance.now(); + // 动画结束后的延迟 scroll 事件:scrollTop 与动画目标一致,说明是动画尾声,忽略 + if ( + lastSlashAnimatedScroll !== null && + now - lastSlashAnimationEndTime < 150 && + Math.abs(list.scrollTop - lastSlashAnimatedScroll) < 1 + ) { + return; + } slashManualScrolled.value = true; }; @@ -766,6 +787,9 @@ const closeSlashMenu = () => { cancelAnimationFrame(slashAnimId); slashAnimId = null; } + slashAnimating = false; + lastSlashAnimatedScroll = null; + lastSlashAnimationEndTime = 0; skillSlashOpen.value = false; skillSlashQuery.value = ''; skillSlashActiveIndex.value = 0;