From 7fba212c249ffc2dfad13e509ea3efe606fc20e5 Mon Sep 17 00:00:00 2001 From: broisnischal Date: Sun, 5 Jul 2026 11:32:53 +0545 Subject: [PATCH] perf(sidebar): smooth out table-list scrolling - Promote the sidebar scroll container to a compositing layer ([will-change:scroll-position]) so WebKit runs it at the display's native rate (120Hz on ProMotion) instead of the 60Hz timer path, matching the technique already used for the canvas table. - Update the virtual-window state synchronously on scroll instead of deferring the scrollTop read into a rAF, which left the rendered window one frame behind the scrollbar thumb during a drag. - Correct ROW_H (28 -> 27) to match the real row stride (25px row + 2px gap) so virtual spacers don't drift. --- src/lib/components/Sidebar.svelte | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/lib/components/Sidebar.svelte b/src/lib/components/Sidebar.svelte index 67be204a..3c8eb71c 100644 --- a/src/lib/components/Sidebar.svelte +++ b/src/lib/components/Sidebar.svelte @@ -283,9 +283,8 @@ }, 200); } - // Release the pending scroll frame and filter timer when the sidebar unmounts. + // Release the pending filter timer when the sidebar unmounts. $effect(() => () => { - if (scrollRaf != null) cancelAnimationFrame(scrollRaf); if (filterDebounce) clearTimeout(filterDebounce); }); @@ -398,7 +397,7 @@ } // ── Virtual list (tables only) ─────────────────────────────────────────── const VIRT_THRESHOLD = 40 // kick in early — 40+ tables already benefits from virtualization - const ROW_H = 28 // px — fixed row stride (row height + gap-0.5); drives spacer math + const ROW_H = 27 // px — fixed row stride: 25px row (13px text + py-1.5) + 2px gap-0.5; drives spacer math const VIRT_BUFFER = 12 // extra rows rendered above and below the viewport // The window shifts one row at a time: `virtStart` is floored to ROW_H, so the // derived already short-circuits (returns the same value) for every scroll event @@ -411,15 +410,13 @@ let tableListEl = $state(null) let sidebarScrollTop = $state(0) let sidebarHeight = $state(0) - /** RAF handle so scroll events update the virtual-window state at most once per - * frame instead of on every (60–120Hz) scroll event. @type {number | null} */ - let scrollRaf = null + // Update the virtual window synchronously on scroll. Reading scrollTop here is a + // cheap cached read (layout is already up to date inside a scroll handler) and + // Svelte coalesces the resulting re-renders into a single flush per frame. A RAF + // hop would only push the rendered window one frame *behind* the scrollbar thumb, + // which reads as lag while dragging. function onSidebarScroll() { - if (scrollRaf != null) return - scrollRaf = requestAnimationFrame(() => { - scrollRaf = null - if (scrollContainerEl) sidebarScrollTop = scrollContainerEl.scrollTop - }) + if (scrollContainerEl) sidebarScrollTop = scrollContainerEl.scrollTop } /** Offset of the tables