@@ -348,6 +348,7 @@ export interface VirtualizerOptions<
348348 enabled ?: boolean
349349 isRtl ?: boolean
350350 useAnimationFrameWithResizeObserver ?: boolean
351+ disableScrollAdjustmentOnBackwardScroll ?: boolean
351352}
352353
353354export class Virtualizer <
@@ -370,6 +371,8 @@ export class Virtualizer<
370371 scrollOffset : number | null = null
371372 scrollDirection : ScrollDirection | null = null
372373 private scrollAdjustments = 0
374+ private pendingAdjustmentDeltas : Array < number > = [ ]
375+ private adjustmentBatchTimer : number | null = null
373376 shouldAdjustScrollPositionOnItemSizeChange :
374377 | undefined
375378 | ( (
@@ -447,6 +450,7 @@ export class Virtualizer<
447450 isRtl : false ,
448451 useScrollendEvent : false ,
449452 useAnimationFrameWithResizeObserver : false ,
453+ disableScrollAdjustmentOnBackwardScroll : false ,
450454 ...opts ,
451455 }
452456 }
@@ -483,6 +487,14 @@ export class Virtualizer<
483487 this . unsubs . filter ( Boolean ) . forEach ( ( d ) => d ! ( ) )
484488 this . unsubs = [ ]
485489 this . observer . disconnect ( )
490+
491+ // Clear adjustment batch timer
492+ if ( this . adjustmentBatchTimer !== null && this . targetWindow ) {
493+ this . targetWindow . cancelAnimationFrame ( this . adjustmentBatchTimer )
494+ this . adjustmentBatchTimer = null
495+ }
496+ this . pendingAdjustmentDeltas = [ ]
497+
486498 this . scrollElement = null
487499 this . targetWindow = null
488500 }
@@ -879,6 +891,21 @@ export class Virtualizer<
879891 }
880892
881893 if ( node . isConnected ) {
894+ // Check if we should skip remeasuring during backward scroll
895+ if (
896+ this . options . disableScrollAdjustmentOnBackwardScroll &&
897+ this . scrollDirection === 'backward' &&
898+ this . isScrolling
899+ ) {
900+ const isAlreadyMeasured = this . itemSizeCache . has ( key )
901+ if ( isAlreadyMeasured ) {
902+ // Skip remeasuring to prevent stuttering during backward scroll
903+ // Use cached measurement instead
904+ return
905+ }
906+ }
907+
908+ // Measure and update size
882909 this . resizeItem ( index , this . options . measureElement ( node , entry , this ) )
883910 }
884911 }
@@ -892,19 +919,19 @@ export class Virtualizer<
892919 const delta = size - itemSize
893920
894921 if ( delta !== 0 ) {
895- if (
922+ const shouldAdjust =
896923 this . shouldAdjustScrollPositionOnItemSizeChange !== undefined
897924 ? this . shouldAdjustScrollPositionOnItemSizeChange ( item , delta , this )
898925 : item . start < this . getScrollOffset ( ) + this . scrollAdjustments
899- ) {
926+
927+ if ( shouldAdjust ) {
900928 if ( process . env . NODE_ENV !== 'production' && this . options . debug ) {
901929 console . info ( 'correction' , delta )
902930 }
903931
904- this . _scrollToOffset ( this . getScrollOffset ( ) , {
905- adjustments : ( this . scrollAdjustments += delta ) ,
906- behavior : undefined ,
907- } )
932+ // Add to batch instead of immediate adjustment
933+ this . pendingAdjustmentDeltas . push ( delta )
934+ this . scheduleAdjustmentBatch ( )
908935 }
909936
910937 this . pendingMeasuredCacheIndexes . push ( item . index )
@@ -914,6 +941,45 @@ export class Virtualizer<
914941 }
915942 }
916943
944+ private scheduleAdjustmentBatch = ( ) => {
945+ if ( ! this . targetWindow ) return
946+
947+ // Cancel existing timer if any
948+ if ( this . adjustmentBatchTimer !== null ) {
949+ this . targetWindow . cancelAnimationFrame ( this . adjustmentBatchTimer )
950+ }
951+
952+ // Schedule batch flush on next animation frame
953+ this . adjustmentBatchTimer = this . targetWindow . requestAnimationFrame ( ( ) => {
954+ this . flushAdjustments ( )
955+ } )
956+ }
957+
958+ private flushAdjustments = ( ) => {
959+ if ( this . pendingAdjustmentDeltas . length === 0 ) {
960+ this . adjustmentBatchTimer = null
961+ return
962+ }
963+
964+ // Sum all pending deltas
965+ const totalDelta = this . pendingAdjustmentDeltas . reduce (
966+ ( sum , delta ) => sum + delta ,
967+ 0 ,
968+ )
969+
970+ // Clear batch
971+ this . pendingAdjustmentDeltas = [ ]
972+ this . adjustmentBatchTimer = null
973+
974+ // Apply single adjustment for all batched changes
975+ if ( totalDelta !== 0 ) {
976+ this . _scrollToOffset ( this . getScrollOffset ( ) , {
977+ adjustments : ( this . scrollAdjustments += totalDelta ) ,
978+ behavior : undefined ,
979+ } )
980+ }
981+ }
982+
917983 measureElement = ( node : TItemElement | null | undefined ) => {
918984 if ( ! node ) {
919985 this . elementsCache . forEach ( ( cached , key ) => {
0 commit comments