Skip to content

refactor: trigger range fetching before items reach viewport#9480

Merged
vursen merged 10 commits into
mainfrom
fix/grid-viewport-range-page-alignment
Jun 18, 2026
Merged

refactor: trigger range fetching before items reach viewport#9480
vursen merged 10 commits into
mainfrom
fix/grid-viewport-range-page-alignment

Conversation

@vursen

@vursen vursen commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

The grid web component fires a page request to the data provider only when a row from that page gets rendered in the DOM. Combined with the limited size of the viewport buffer, this often leads to the grid showing blank rows even when scrolling at a relatively slow speed, since pages can't load fast enough to keep up. Then, when the response arrives, many rendered rows get updated at once, triggering a heavy synchronous DOM update that blocks the event loop and has a negative impact on FPS. This effect has become even more noticeable after vaadin/web-components#11196, which reduced the number of rendered rows outside the viewport. So while render time metrics improved after that fix, scroll frame time metrics regressed.

This PR makes the connector request data ahead of rendering while the user scrolls. On every virtualizer row update during scrolling, the connector computes the fetch range (the rendered range expanded by one viewport of buffer in both directions and aligned to page boundaries) and ensures its first and last indexes are requested from the data provider. If a boundary page is missing from the cache, this in turn triggers a server request that fetches the whole computed range. As a result, buffer pages start loading before their rows get rendered, so rows entering the viewport are much more likely to already have data, and row updates arrive in smaller batches. When the fetch range is already cached, the calls do nothing.

The prefetch only runs while the scroller has the scrolling attribute. During other row updates (initial render, server-side data changes), requests driven by rendered rows are enough, and prefetching there would only produce redundant requests for pages the server chose not to send.

Before:

Before.mov

After:

After.mov

Depends on

@vursen vursen changed the title refactor: trigger range loading before items reach viewport refactor: trigger range fetching before items reach viewport Jun 10, 2026
@vursen vursen changed the title refactor: trigger range fetching before items reach viewport refactor: trigger range fetching in advance while scrolling Jun 10, 2026
@vursen vursen changed the title refactor: trigger range fetching in advance while scrolling refactor: trigger range fetching before items reach viewport Jun 10, 2026
@vursen vursen force-pushed the fix/grid-viewport-range-page-alignment branch 2 times, most recently from a8fc44a to f034d3e Compare June 11, 2026 10:57
@vursen vursen changed the base branch from main to fix/grid-fetch-range-extra-page June 11, 2026 10:58
@vursen vursen force-pushed the fix/grid-viewport-range-page-alignment branch 3 times, most recently from d606457 to 6b8796d Compare June 11, 2026 11:42
Base automatically changed from fix/grid-fetch-range-extra-page to main June 12, 2026 09:20
@vursen vursen force-pushed the fix/grid-viewport-range-page-alignment branch 3 times, most recently from 84ffad6 to b85a8d5 Compare June 16, 2026 13:24
vursen and others added 6 commits June 16, 2026 18:13
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`_updateScrollerItem` ran the fetch-range load whenever a column
tree existed, triggering loads during non-scroll updates. Gate it on
the scrolling state (`overscrollBehavior === 'auto'`) so the load only
happens while the user is actually scrolling.
The web component renamed `_updateScrollerItem` to
`__updateVirtualizerElement` and no longer toggles the
`overscrollBehavior` style while scrolling. Hook the new method and
use the scroller's `scrolling` attribute instead.
The test currently fails because the connector hooks
`__updateVirtualizerElement`, which is only available in unreleased
@vaadin/grid (vaadin/web-components#11914). It should pass once the
dependency is updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vursen vursen force-pushed the fix/grid-viewport-range-page-alignment branch from b85a8d5 to de3c551 Compare June 16, 2026 14:13
vursen added 2 commits June 16, 2026 20:25
These tests covered behavior removed by the viewport range
fix; the connector no longer reloads a range based on which
part was cleared.
Comment on lines -191 to -235
describe('last requested range is not in viewport', () => {
beforeEach(async () => {
// Request a range of items further down
clear(grid.$connector, 50, 50);
grid.scrollToIndex(50);
await aTimeout(GRID_CONNECTOR_ROOT_REQUEST_DELAY);
expect(grid.$server.setViewportRange).to.have.been.calledOnceWith(30, 50);
setRootItems(grid.$connector, items, 30, 50);
grid.$server.setViewportRange.resetHistory();
});

it('should request for items if part of the last range was cleared', async () => {
// Simulate preloading of items when scrolling to top programmatically on server-side, which may also partially clear the last requested range:
// - Scroll to top
// - Clear last requested range partially
// - Preload first two pages so that grid doesn't need to request a new range yet
grid.scrollToIndex(0);
clear(grid.$connector, 40, grid.pageSize);
setRootItems(grid.$connector, items, 0, 30);
await aTimeout(GRID_CONNECTOR_ROOT_REQUEST_DELAY);
expect(grid.$server.setViewportRange).to.not.have.been.called;

// Scroll down again, should reload the range because part of it was cleared
grid.scrollToIndex(50);
await aTimeout(GRID_CONNECTOR_ROOT_REQUEST_DELAY);
expect(grid.$server.setViewportRange).to.have.been.calledOnceWith(30, 50);
});

it('should not request for items if data outside of the last range was cleared', async () => {
// Simulate preloading of items when scrolling to top programmatically on server-side, which may also partially clear the last requested range:
// - Scroll to top
// - Clear data outside the requested range
// - Preload first two pages so that grid doesn't need to request a new range yet
grid.scrollToIndex(0);
clear(grid.$connector, 70, grid.pageSize);
grid.$connector.confirm(-1);
await aTimeout(GRID_CONNECTOR_ROOT_REQUEST_DELAY);
expect(grid.$server.setViewportRange).to.not.have.been.called;

// Scroll down again, should not reload the range because nothing from it was cleared
grid.scrollToIndex(50);
await aTimeout(GRID_CONNECTOR_ROOT_REQUEST_DELAY);
expect(grid.$server.setViewportRange).to.not.have.been.called;
});
});

@vursen vursen Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests can be removed.

They were added in #6010 to cover the sanitizeLastRequestedRange logic, which decided whether to reload a range based on which part of it had been cleared. That logic was later removed in #7896, so these tests no longer exercise any real connector behavior.

The connector now loads the cached fetch range (rendered rows + buffer) while scrolling, and that behavior is already covered in grid-connector-data-range.test.ts.

@vursen vursen requested review from sissbruecker and tomivirkki and removed request for sissbruecker June 17, 2026 08:39
@vursen vursen marked this pull request as ready for review June 17, 2026 08:39
@vursen

vursen commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request

Copilot AI added 2 commits June 17, 2026 11:54
…ge-page-alignment

# Conflicts:
#	vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/test/shared.ts

Copilot AI commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

Resolved in 1966cf0.

@sonarqubecloud

Copy link
Copy Markdown

@vursen vursen added this pull request to the merge queue Jun 18, 2026
Merged via the queue into main with commit b05c18f Jun 18, 2026
19 checks passed
@vursen vursen deleted the fix/grid-viewport-range-page-alignment branch June 18, 2026 09:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants