Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,28 @@ getPageQuery: ({limit, start, dir, settled}) => ({
}),
```

### Page size

The `limit` passed to `getPageQuery` comes from the page size: about three
viewports' worth of rows at `estimateSize`, but never below the `minPageSize`
floor (default 100). The floor is sized for short rows; for tall rows (cards,
comments) 100 rows is many viewports of content, so the floor dominates the
formula and each page load renders far more DOM than needed — showing up as
long tasks when a page lands mid-scroll. Lower it so page size tracks the
viewport again:

```ts
useZeroVirtualizer({
estimateSize: () => 200, // tall cards
minPageSize: 20,
// ...
});
```

Smaller pages trade a few more query round trips for smaller, smoother
per-page render bursts. The page size never shrinks once grown, and is rounded
up to an even number (paging splits pages in half around permalinks).

### Scroll settling

`useZeroVirtualizer` tracks whether the user has stopped scrolling:
Expand Down
17 changes: 17 additions & 0 deletions src/core/virtualizer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,3 +409,20 @@ describe('ZeroVirtualizer wrapper contract', () => {
});
});
});

describe('minPageSize', () => {
test('defaults the query page size to 100', () => {
const v = new ZeroVirtualizer(makeOptions());
expect(v.getQueryInputs().pageSize).toBe(100);
});

test('lowers the floor for tall rows', () => {
const v = new ZeroVirtualizer(makeOptions({minPageSize: 20}));
expect(v.getQueryInputs().pageSize).toBe(20);
});

test('is rounded up to an even number (paging halves pages)', () => {
const v = new ZeroVirtualizer(makeOptions({minPageSize: 15}));
expect(v.getQueryInputs().pageSize).toBe(16);
});
});
28 changes: 22 additions & 6 deletions src/core/virtualizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,14 @@ export type VirtualizerOptions<TListContextParams, TRow, TStartRow> = {
count?: number | undefined;
permalinkID?: string | null | undefined;
settleTime?: number | undefined;
/**
* Floor for the query page size. The page size is derived from the viewport
* (about three viewports' worth of rows at `estimateSize`), but never drops
* below this floor. Defaults to 100 — sized for short rows; lower it for
* tall rows (cards, comments) where 100 rows is many viewports of content.
* Rounded up to an even number (paging halves pages around permalinks).
*/
minPageSize?: number | undefined;
scrollState?: ScrollHistoryState<TStartRow> | null | undefined;
onScrollStateChange?:
| ((state: ScrollHistoryState<TStartRow>) => void)
Expand Down Expand Up @@ -233,7 +241,9 @@ export class ZeroVirtualizer<TListContextParams, TRow, TStartRow> {

// ---- paging state ---------------------------------------------------------
#paging: PagingState<TListContextParams, TStartRow>;
#pageSize = MIN_PAGE_SIZE;
// Assigned from #minPageSize() in the constructor (field initializers run
// before #options is set); grows monotonically in #updatePageSize.
#pageSize: number;
#settled = false;

// ---- change propagation ---------------------------------------------------
Expand Down Expand Up @@ -318,6 +328,7 @@ export class ZeroVirtualizer<TListContextParams, TRow, TStartRow> {
) {
this.#resolveScrollElement = resolveScrollElement;
this.#options = options;
this.#pageSize = this.#minPageSize();
// Initialize paging directly from the restorable state so the first
// render already queries the right window (this also survives React
// Strict Mode's double construction — the constructor is pure).
Expand Down Expand Up @@ -1003,16 +1014,21 @@ export class ZeroVirtualizer<TListContextParams, TRow, TStartRow> {

// ---- rows/options-driven transitions (afterDOMUpdate) -----------------------

// The configured page-size floor: `minPageSize` (evened — paging halves
// pages around permalinks), defaulting to MIN_PAGE_SIZE.
#minPageSize(): number {
const min = this.#options.minPageSize;
return min === undefined ? MIN_PAGE_SIZE : makeEven(Math.max(2, min));
}
Comment on lines +1019 to +1022

#updatePageSize(): void {
const min = this.#minPageSize();
const el = this.#el;
const height = el ? this.#viewportRect(el).height : 0;
const newPageSize =
height > 0
? Math.max(
MIN_PAGE_SIZE,
makeEven(Math.ceil(height / this.#rowEstimate()) * 3),
)
: MIN_PAGE_SIZE;
? Math.max(min, makeEven(Math.ceil(height / this.#rowEstimate()) * 3))
: min;
if (newPageSize > this.#pageSize) {
this.#pageSize = newPageSize;
this.#version++;
Expand Down
Loading