diff --git a/src/components/json-text-view.spec.ts b/src/components/json-text-view.spec.ts index 2595af0..a87ada7 100644 --- a/src/components/json-text-view.spec.ts +++ b/src/components/json-text-view.spec.ts @@ -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(); + } + }); }); diff --git a/src/components/json-text-view.vue b/src/components/json-text-view.vue index 62369c8..ec09637 100644 --- a/src/components/json-text-view.vue +++ b/src/components/json-text-view.vue @@ -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 的窗口更新足够视觉流畅,重计算频率减半 /* ---------- 可视窗口(纯函数,阶段二已单测锁死) ---------- */