You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: Add built-in scroll-scaling to bypass browser scroll height limits
6
+
7
+
Browsers cap `scrollHeight` at approximately 33.5 million pixels, making items at the end of very large lists unreachable. The virtualizer now automatically detects when the total virtual size exceeds the configurable `maxScrollSize` (default: 33,000,000 px) and applies a transparent scale transform to compress the scroll range.
8
+
9
+
**New option:**
10
+
-`maxScrollSize` — Maximum physical scroll container size in pixels. Set to `Infinity` to disable scaling. Default: `33_000_000`.
11
+
12
+
**New property:**
13
+
-`scale` — The current scale factor (1 when no scaling is active).
14
+
15
+
When scaling is active:
16
+
-`getTotalSize()` returns the capped physical size for use as the container's CSS height/width.
17
+
-`getVirtualItems()` returns items with physical coordinates — use `item.start` directly for positioning.
18
+
-`scrollToIndex()`, `scrollToOffset()`, and `scrollBy()` work transparently.
19
+
- Scroll anchoring (resize adjustments for items above the viewport) works correctly through the scale transform.
20
+
21
+
When scaling is **not** active (the vast majority of use cases), there is zero overhead — the existing code paths are unchanged.
22
+
23
+
This feature is implemented entirely in `virtual-core` and works across all framework adapters (React, Vue, Solid, Svelte, Angular, Lit) with no adapter changes required.
Copy file name to clipboardExpand all lines: docs/api/virtualizer.md
+51Lines changed: 51 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -245,6 +245,43 @@ Controls when lane assignments are cached in a masonry layout.
245
245
-`'estimate'` (default): lane assignments are cached immediately based on `estimateSize`. This keeps items from jumping between lanes, but assignments may be suboptimal when the estimate is inaccurate.
246
246
-`'measured'`: lane caching is deferred until items are measured via `measureElement`, so assignments reflect actual measured sizes. After the initial measurement, lanes are cached and remain stable.
247
247
248
+
### `maxScrollSize`
249
+
250
+
```tsx
251
+
maxScrollSize?:number
252
+
```
253
+
254
+
**Default**: `33_000_000`
255
+
256
+
Maximum physical scroll container size in pixels. Browsers cap `scrollHeight` at approximately 33.5 million pixels. When the total virtual size of all items exceeds `maxScrollSize`, the virtualizer automatically applies a scale factor to compress the scroll range so that all items remain reachable.
257
+
258
+
When scaling is active:
259
+
-`getTotalSize()` returns the capped physical size (use this for your container's CSS height/width)
260
+
-`getVirtualItems()` returns items with physical coordinates (use `item.start` directly for `translateY`/`translateX`)
261
+
-`scrollToIndex()` and `scrollToOffset()` work transparently
262
+
- The `scale` property reflects the current scale factor
263
+
264
+
Set to `Infinity` to disable scaling entirely.
265
+
266
+
```tsx
267
+
// Example: 1 million items at 40px each = 40M px (exceeds browser limit)
268
+
const virtualizer =useVirtualizer({
269
+
count: 1_000_000,
270
+
estimateSize: () =>40,
271
+
getScrollElement: () =>parentRef.current,
272
+
// maxScrollSize defaults to 33M — scaling activates automatically
273
+
})
274
+
275
+
// Everything works as normal — no code changes needed:
276
+
<divstyle={{ height: virtualizer.getTotalSize() }}> {/* capped at ~33M */}
Returns the total size in pixels for the virtualized items. This measurement will incrementally change if you choose to dynamically measure your elements as they are rendered.
399
436
437
+
When scroll-scaling is active (i.e., the virtual total exceeds `maxScrollSize`), this returns the capped physical size suitable for use as the container's CSS height/width. Use the `scale` property to recover the uncapped virtual total if needed.
438
+
400
439
### `measure`
401
440
402
441
```tsx
@@ -511,3 +550,15 @@ scrollOffset: number
511
550
```
512
551
513
552
This option represents the current scroll position along the scrolling axis. It is measured in pixels from the starting point of the scrollable area.
553
+
554
+
When scroll-scaling is active, this value is in virtual (unscaled) coordinate space, which may be larger than the physical scroll position reported by the browser.
555
+
556
+
### `scale`
557
+
558
+
```tsx
559
+
scale: number
560
+
```
561
+
562
+
The current scale factor applied by the virtualizer. Returns `1` when the total virtual size is within the `maxScrollSize` limit (no scaling needed). When scaling is active, this value is greater than `1`.
563
+
564
+
You can use this to recover the real (unscaled) size of an item: `realSize = item.size * virtualizer.scale`.
0 commit comments