From c3bf8864b9d4c82506c01bf9f47f8977bfbc197b Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Sat, 19 Sep 2026 10:38:33 +1000 Subject: [PATCH] perf(ui): let the resize observer take the first scrollbar measurement (#49778) --- packages/ui/src/components/scroll-view.tsx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/ui/src/components/scroll-view.tsx b/packages/ui/src/components/scroll-view.tsx index 9b675d5ece7..a2e4cc1a504 100644 --- a/packages/ui/src/components/scroll-view.tsx +++ b/packages/ui/src/components/scroll-view.tsx @@ -1,4 +1,4 @@ -import { createEffect, mergeProps, onCleanup, onMount, Show, splitProps, type ComponentProps } from "solid-js" +import { createEffect, mergeProps, on, onCleanup, onMount, Show, splitProps, type ComponentProps } from "solid-js" import { Portal } from "solid-js/web" import { createResizeObserver } from "@solid-primitives/resize-observer" import { createStore } from "solid-js/store" @@ -236,18 +236,22 @@ export function ScrollView(props: ScrollViewProps) { local.viewportRef(viewportRef) } + // The observer reports every target once when it starts observing (including a thumb mount that + // appears later), after layout. Measuring here as well would force a synchronous layout per + // ScrollView while the page is still rendering. createResizeObserver( () => [viewportRef, viewportRef.firstElementChild, thumbMount()].filter(Boolean) as HTMLElement[], updateThumb, ) - - updateThumb() }) - createEffect(() => { - thumbMount() - updateThumb() - }) + // Inputs that change the thumb without resizing anything: re-measure on change, but leave the + // first measurement to the observer. + createEffect( + on([() => local.verticalScrollAdjustment, vertical, horizontal, thumbMount], () => updateThumb(), { + defer: true, + }), + ) createEffect(() => { if (!horizontal() || !viewportRef) return