Archived
fix(view): 拖拽滚动条白屏——节流补 trailing,滚动窗口持续跟随
useThrottleFn 第三参 trailing 默认 false,拖拽滚动条的连续 scroll 事件(间隔恒 ≤33ms)在节流窗口内被全部丢弃不补执行,scrollTopPx 停在起拖位置导致白屏;开启 trailing 后窗口每 33ms 补执行一次跟随滚动。新增拖拽场景测试(连续 scroll + fake timers 断言窗口跟随最终位置),曾用 stash 对照验证旧行为下测试失败。
This commit is contained in:
@@ -78,4 +78,40 @@ describe('json-text-view 虚拟滚动', () => {
|
|||||||
]);
|
]);
|
||||||
wrapper.unmount();
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -42,11 +42,15 @@ const { height: containerHeightPx } = useElementSize(scrollContainerRef);
|
|||||||
|
|
||||||
const scrollTopPx = ref(0);
|
const scrollTopPx = ref(0);
|
||||||
|
|
||||||
const handleScroll = useThrottleFn(() => {
|
const handleScroll = useThrottleFn(
|
||||||
|
() => {
|
||||||
if (scrollContainerRef.value) {
|
if (scrollContainerRef.value) {
|
||||||
scrollTopPx.value = scrollContainerRef.value.scrollTop;
|
scrollTopPx.value = scrollContainerRef.value.scrollTop;
|
||||||
}
|
}
|
||||||
}, 33); // ~30fps 的窗口更新足够视觉流畅,重计算频率减半
|
},
|
||||||
|
33,
|
||||||
|
true, // trailing 必开:拖拽滚动条是连续事件(间隔恒 ≤33ms),默认 false 会把窗口内事件全部丢弃不补执行 → 白屏
|
||||||
|
); // ~30fps 的窗口更新足够视觉流畅,重计算频率减半
|
||||||
|
|
||||||
/* ---------- 可视窗口(纯函数,阶段二已单测锁死) ---------- */
|
/* ---------- 可视窗口(纯函数,阶段二已单测锁死) ---------- */
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user