From 4d34a5d01dec23c242e8b0c4f8b50c175bfc2133 Mon Sep 17 00:00:00 2001 From: dracofulmen Date: Tue, 23 Jun 2026 17:31:14 -0600 Subject: [PATCH 1/9] Scrolling fix Added check to observeElementOffset to prevent continuous re-renders --- packages/virtual-core/src/index.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/virtual-core/src/index.ts b/packages/virtual-core/src/index.ts index 7af791d1..1b180804 100644 --- a/packages/virtual-core/src/index.ts +++ b/packages/virtual-core/src/index.ts @@ -779,6 +779,13 @@ export class Virtualizer< // self-write — by the time the user has moved 1.5 px, the // intended value will already have been consumed by a prior // scroll event and cleared. + if (offset === this.scrollOffset && isScrolling === true) { + // this prevents continous rerenders + isScrolling = false; + this.isScrolling = false; + this.scrollOffset = offset + this.maybeNotify(); + } if ( this._intendedScrollOffset !== null && Math.abs(offset - this._intendedScrollOffset) < 1.5 @@ -804,7 +811,6 @@ export class Virtualizer< if (this.scrollState) { this.scheduleScrollReconcile() } - this.maybeNotify() }), ) From 4a61b19f443335a7c2b3922c2569e10a473df897 Mon Sep 17 00:00:00 2001 From: dracofulmen Date: Tue, 23 Jun 2026 17:46:18 -0600 Subject: [PATCH 2/9] Added changeset --- .changeset/cold-ads-lose.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cold-ads-lose.md diff --git a/.changeset/cold-ads-lose.md b/.changeset/cold-ads-lose.md new file mode 100644 index 00000000..262cb5a3 --- /dev/null +++ b/.changeset/cold-ads-lose.md @@ -0,0 +1,5 @@ +--- +'@tanstack/virtual-core': patch +--- + +Fixed observeElementOffset to prevent continuous rerenders during or after scrolling From 77ebf7b5e99e9f12ad003797274ce62b69b29ccc Mon Sep 17 00:00:00 2001 From: dracofulmen <76636432+dracofulmen@users.noreply.github.com> Date: Thu, 25 Jun 2026 13:01:19 -0600 Subject: [PATCH 3/9] Update packages/virtual-core/src/index.ts Co-authored-by: Damian Pieczynski --- packages/virtual-core/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/virtual-core/src/index.ts b/packages/virtual-core/src/index.ts index 1b180804..633f7465 100644 --- a/packages/virtual-core/src/index.ts +++ b/packages/virtual-core/src/index.ts @@ -811,6 +811,7 @@ export class Virtualizer< if (this.scrollState) { this.scheduleScrollReconcile() } + this.maybeNotify() }), ) From 0e503102784372297421e97d2a0b90aac18cba2c Mon Sep 17 00:00:00 2001 From: dracofulmen <76636432+dracofulmen@users.noreply.github.com> Date: Thu, 25 Jun 2026 13:01:27 -0600 Subject: [PATCH 4/9] Update packages/virtual-core/src/index.ts Co-authored-by: Damian Pieczynski --- packages/virtual-core/src/index.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/virtual-core/src/index.ts b/packages/virtual-core/src/index.ts index 633f7465..9f197597 100644 --- a/packages/virtual-core/src/index.ts +++ b/packages/virtual-core/src/index.ts @@ -772,6 +772,21 @@ export class Virtualizer< this.unsubs.push( this.options.observeElementOffset(this, (offset, isScrolling) => { + // A scroll event that reports movement but lands on the offset we + // already hold — and isn't a self-write read-back — is a spurious + // no-op re-emit that Safari/Firefox fire after a re-render's layout + // (Chrome doesn't). Treating it as scrolling re-arms `isScrolling`, + // which forces a render that triggers another such event: an + // infinite re-render loop. Ignore it. (Self-writes are handled by + // the `_intendedScrollOffset` reconciliation just below.) + if ( + isScrolling && + this._intendedScrollOffset === null && + offset === this.scrollOffset + ) { + return + } + // If this scroll event looks like the browser's read-back of a // value we just wrote, prefer our intended (sub-pixel-accurate) // value over the browser's rounded one. The 1.5 px tolerance is From e25a986ca2c089393853266c852d2fab4398d425 Mon Sep 17 00:00:00 2001 From: dracofulmen <76636432+dracofulmen@users.noreply.github.com> Date: Thu, 25 Jun 2026 13:01:35 -0600 Subject: [PATCH 5/9] Update packages/virtual-core/src/index.ts Co-authored-by: Damian Pieczynski --- packages/virtual-core/src/index.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/virtual-core/src/index.ts b/packages/virtual-core/src/index.ts index 9f197597..5ac62154 100644 --- a/packages/virtual-core/src/index.ts +++ b/packages/virtual-core/src/index.ts @@ -794,13 +794,6 @@ export class Virtualizer< // self-write — by the time the user has moved 1.5 px, the // intended value will already have been consumed by a prior // scroll event and cleared. - if (offset === this.scrollOffset && isScrolling === true) { - // this prevents continous rerenders - isScrolling = false; - this.isScrolling = false; - this.scrollOffset = offset - this.maybeNotify(); - } if ( this._intendedScrollOffset !== null && Math.abs(offset - this._intendedScrollOffset) < 1.5 From 2b4c600544b4bfcc6a56a44c30e7bc2bc3884c64 Mon Sep 17 00:00:00 2001 From: dracofulmen Date: Thu, 25 Jun 2026 13:42:27 -0600 Subject: [PATCH 6/9] Update index.ts This prevents hiccups during scrolling itself. --- packages/virtual-core/src/index.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/virtual-core/src/index.ts b/packages/virtual-core/src/index.ts index 5ac62154..5ed12b93 100644 --- a/packages/virtual-core/src/index.ts +++ b/packages/virtual-core/src/index.ts @@ -779,11 +779,7 @@ export class Virtualizer< // which forces a render that triggers another such event: an // infinite re-render loop. Ignore it. (Self-writes are handled by // the `_intendedScrollOffset` reconciliation just below.) - if ( - isScrolling && - this._intendedScrollOffset === null && - offset === this.scrollOffset - ) { + if (isScrolling && offset === this.scrollOffset) { return } From 8bafcdb6e8b147d53cb90e98e81d212c191cf506 Mon Sep 17 00:00:00 2001 From: dracofulmen Date: Thu, 25 Jun 2026 17:46:20 -0600 Subject: [PATCH 7/9] Revert "Update index.ts" This reverts commit 2b4c600544b4bfcc6a56a44c30e7bc2bc3884c64. --- packages/virtual-core/src/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/virtual-core/src/index.ts b/packages/virtual-core/src/index.ts index 5ed12b93..5ac62154 100644 --- a/packages/virtual-core/src/index.ts +++ b/packages/virtual-core/src/index.ts @@ -779,7 +779,11 @@ export class Virtualizer< // which forces a render that triggers another such event: an // infinite re-render loop. Ignore it. (Self-writes are handled by // the `_intendedScrollOffset` reconciliation just below.) - if (isScrolling && offset === this.scrollOffset) { + if ( + isScrolling && + this._intendedScrollOffset === null && + offset === this.scrollOffset + ) { return } From 6ce9b8c34a8ab4ebdcc42d4e9b232b5b6acd41e9 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 05:30:04 +0000 Subject: [PATCH 8/9] ci: apply automated fixes --- packages/virtual-core/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/virtual-core/src/index.ts b/packages/virtual-core/src/index.ts index 5ac62154..f9573aa9 100644 --- a/packages/virtual-core/src/index.ts +++ b/packages/virtual-core/src/index.ts @@ -786,7 +786,7 @@ export class Virtualizer< ) { return } - + // If this scroll event looks like the browser's read-back of a // value we just wrote, prefer our intended (sub-pixel-accurate) // value over the browser's rounded one. The 1.5 px tolerance is From d818cd190dc187acee60f0778f08c754c9e27e98 Mon Sep 17 00:00:00 2001 From: Damian Pieczynski Date: Fri, 26 Jun 2026 07:39:58 +0200 Subject: [PATCH 9/9] Update cold-ads-lose.md --- .changeset/cold-ads-lose.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/cold-ads-lose.md b/.changeset/cold-ads-lose.md index 262cb5a3..dfb2db68 100644 --- a/.changeset/cold-ads-lose.md +++ b/.changeset/cold-ads-lose.md @@ -2,4 +2,4 @@ '@tanstack/virtual-core': patch --- -Fixed observeElementOffset to prevent continuous rerenders during or after scrolling +Skip redundant scroll events at unchanged offset