diff --git a/frontend/src/views/KnowledgeView.vue b/frontend/src/views/KnowledgeView.vue index 8ca13a4..0463746 100644 --- a/frontend/src/views/KnowledgeView.vue +++ b/frontend/src/views/KnowledgeView.vue @@ -245,15 +245,21 @@ function formatDate(iso: string): string { // ─── Infinite scroll ────────────────────────────────────────────────────────── const cardGridEl = ref(null); +let scrollRafId: number | null = null; function onGridScroll() { - const el = cardGridEl.value; - if (!el || loading.value || items.value.length >= total.value) return; - const nearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 300; - if (nearBottom) { - page.value++; - fetchItems(); - } + // Debounce via rAF — coalesces rapid scroll events into one check per frame + if (scrollRafId !== null) return; + scrollRafId = requestAnimationFrame(() => { + scrollRafId = null; + const el = cardGridEl.value; + if (!el || loading.value || items.value.length >= total.value) return; + const nearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 300; + if (nearBottom) { + page.value++; + fetchItems(); + } + }); } // ─── Lifecycle ──────────────────────────────────────────────────────────────── @@ -266,6 +272,7 @@ onMounted(() => { onUnmounted(() => { if (searchDebounce) clearTimeout(searchDebounce); + if (scrollRafId !== null) cancelAnimationFrame(scrollRafId); });