fix(input): /菜单上下键滚动抖动与高亮条偶发闪烁

- 修复 rAF 第一帧 now 早于 startTime 导致 scrollTop 反向抖动
- 过滤动画结束后的延迟 scroll 事件,避免误进入 manualScrolled 模式导致高亮条闪一下消失
This commit is contained in:
JOJO 2026-06-19 21:24:47 +08:00
parent 2766a8ab57
commit 2c6d208be7

View File

@ -597,6 +597,11 @@ const slashHighlightTop = ref(4);
/** 手动滚动后切到鼠标 hover 模式,隐藏 JS 高亮条 */ /** 手动滚动后切到鼠标 hover 模式,隐藏 JS 高亮条 */
const slashManualScrolled = ref(false); const slashManualScrolled = ref(false);
let slashAnimId: number | null = null; let slashAnimId: number | null = null;
/** 动画真正进行中(比 animId 更可靠,可覆盖 rAF 结束后的延迟 scroll 事件) */
let slashAnimating = false;
/** 记录最近一次动画的目标 scrollTop 和结束时间,用于过滤动画后的延迟 scroll 事件 */
let lastSlashAnimatedScroll: number | null = null;
let lastSlashAnimationEndTime = 0;
const slashHighlightStyle = computed(() => { const slashHighlightStyle = computed(() => {
if (slashManualScrolled.value) return { display: 'none' }; if (slashManualScrolled.value) return { display: 'none' };
@ -615,6 +620,8 @@ const animateSlash = (element: HTMLElement, targetScroll: number) => {
if (interrupted) { if (interrupted) {
cancelAnimationFrame(slashAnimId); cancelAnimationFrame(slashAnimId);
} }
slashAnimating = true;
lastSlashAnimatedScroll = targetScroll;
const startScroll = element.scrollTop; const startScroll = element.scrollTop;
const startHighlight = slashHighlightTop.value; const startHighlight = slashHighlightTop.value;
const distScroll = targetScroll - startScroll; const distScroll = targetScroll - startScroll;
@ -624,7 +631,8 @@ const animateSlash = (element: HTMLElement, targetScroll: number) => {
const startTime = performance.now(); const startTime = performance.now();
const step = (now: number) => { 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 progress = Math.min(elapsed / duration, 1);
const eased = 1 - Math.pow(1 - progress, 3); const eased = 1 - Math.pow(1 - progress, 3);
element.scrollTop = startScroll + distScroll * eased; element.scrollTop = startScroll + distScroll * eased;
@ -633,6 +641,8 @@ const animateSlash = (element: HTMLElement, targetScroll: number) => {
slashAnimId = requestAnimationFrame(step); slashAnimId = requestAnimationFrame(step);
} else { } else {
slashAnimId = null; slashAnimId = null;
slashAnimating = false;
lastSlashAnimationEndTime = performance.now();
} }
}; };
slashAnimId = requestAnimationFrame(step); slashAnimId = requestAnimationFrame(step);
@ -640,7 +650,18 @@ const animateSlash = (element: HTMLElement, targetScroll: number) => {
/** 手动滚动时切到鼠标 hover 模式,不再跟踪键盘选中 */ /** 手动滚动时切到鼠标 hover 模式,不再跟踪键盘选中 */
const onSlashScroll = () => { 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; slashManualScrolled.value = true;
}; };
@ -766,6 +787,9 @@ const closeSlashMenu = () => {
cancelAnimationFrame(slashAnimId); cancelAnimationFrame(slashAnimId);
slashAnimId = null; slashAnimId = null;
} }
slashAnimating = false;
lastSlashAnimatedScroll = null;
lastSlashAnimationEndTime = 0;
skillSlashOpen.value = false; skillSlashOpen.value = false;
skillSlashQuery.value = ''; skillSlashQuery.value = '';
skillSlashActiveIndex.value = 0; skillSlashActiveIndex.value = 0;