fix(view): 拖拽滚动条白屏——节流补 trailing,滚动窗口持续跟随

useThrottleFn 第三参 trailing 默认 false,拖拽滚动条的连续 scroll 事件(间隔恒 ≤33ms)在节流窗口内被全部丢弃不补执行,scrollTopPx 停在起拖位置导致白屏;开启 trailing 后窗口每 33ms 补执行一次跟随滚动。新增拖拽场景测试(连续 scroll + fake timers 断言窗口跟随最终位置),曾用 stash 对照验证旧行为下测试失败。
This commit is contained in:
2026-09-08 13:26:28 +08:00
parent c690e0507e
commit e3b1003f4d
2 changed files with 45 additions and 5 deletions
+36
View File
@@ -78,4 +78,40 @@ describe('json-text-view 虚拟滚动', () => {
]);
wrapper.unmount();
});
it('连续滚动事件(拖拽滚动条场景):节流 trailing 补执行,窗口跟随最终位置不白屏', async () => {
vi.useFakeTimers();
try {
const text = makeJsonText(6000); // ~2.4 万行,scrollTop 任意值都有内容
const wrapper = mount(JsonTextView, {
props: { json: JSON.parse(text), rawText: text },
attachTo: document.body,
});
const container = wrapper.find('.json-text-view');
const scrollLines = () => wrapper.findAll('.line').map((line) => line.find('.gutter').text());
// 模拟拖拽滚动条:连续高频 scroll 事件,间隔均 ≤33ms(全部落在节流窗口内)
const dragSequence = [1000, 2000, 3000, 4000, 5000];
for (const scrollTop of dragSequence) {
Object.defineProperty(container.element, 'scrollTop', {
value: scrollTop,
configurable: true,
});
await container.trigger('scroll');
await wrapper.vm.$nextTick();
}
// 若 trailing 未开启:窗口内容还停在首帧位置(白屏根因);开启后 33ms 内必有一次补执行
vi.advanceTimersByTime(34);
await wrapper.vm.$nextTick();
// 行号 5000/20=250 起(5000px→第 250 行,含 overscan 从 245 起),证明窗口跟随到了最终位置
const firstGutter = scrollLines()[0]!;
expect(Number(firstGutter)).toBeGreaterThanOrEqual(245);
expect(Number(firstGutter)).toBeLessThan(260);
wrapper.unmount();
} finally {
vi.useRealTimers();
}
});
});
+9 -5
View File
@@ -42,11 +42,15 @@ const { height: containerHeightPx } = useElementSize(scrollContainerRef);
const scrollTopPx = ref(0);
const handleScroll = useThrottleFn(() => {
if (scrollContainerRef.value) {
scrollTopPx.value = scrollContainerRef.value.scrollTop;
}
}, 33); // ~30fps 的窗口更新足够视觉流畅,重计算频率减半
const handleScroll = useThrottleFn(
() => {
if (scrollContainerRef.value) {
scrollTopPx.value = scrollContainerRef.value.scrollTop;
}
},
33,
true, // trailing 必开:拖拽滚动条是连续事件(间隔恒 ≤33ms),默认 false 会把窗口内事件全部丢弃不补执行 → 白屏
); // ~30fps 的窗口更新足够视觉流畅,重计算频率减半
/* ---------- 可视窗口(纯函数,阶段二已单测锁死) ---------- */