virtua 0.52.10 → 0.53.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +286 -193
- package/lib/angular/VList.d.ts +3 -1
- package/lib/angular/VMasonry.d.ts +119 -0
- package/lib/angular/Virtualizer.d.ts +3 -1
- package/lib/angular/WindowVirtualizer.d.ts +3 -1
- package/lib/angular/index.d.ts +1 -0
- package/lib/angular/index.js +732 -333
- package/lib/angular/index.js.map +1 -1
- package/lib/core/index.cjs +180 -104
- package/lib/core/index.cjs.map +1 -1
- package/lib/core/index.d.ts +1 -0
- package/lib/core/index.js +257 -181
- package/lib/core/index.js.map +1 -1
- package/lib/core/layouts/masonry.d.ts +1 -0
- package/lib/index.cjs +562 -385
- package/lib/index.cjs.map +1 -1
- package/lib/index.js +579 -404
- package/lib/index.js.map +1 -1
- package/lib/react/VMasonry.d.ts +109 -0
- package/lib/react/Virtualizer.d.ts +3 -1
- package/lib/react/WindowVirtualizer.d.ts +3 -1
- package/lib/react/index.d.ts +2 -0
- package/lib/react/types.d.ts +1 -1
- package/lib/solid/VMasonry.d.ts +111 -0
- package/lib/solid/Virtualizer.d.ts +3 -1
- package/lib/solid/WindowVirtualizer.d.ts +3 -1
- package/lib/solid/index.cjs +726 -514
- package/lib/solid/index.cjs.map +1 -1
- package/lib/solid/index.d.ts +2 -0
- package/lib/solid/index.js +697 -494
- package/lib/solid/index.js.map +1 -1
- package/lib/solid/index.jsx +343 -8
- package/lib/solid/index.jsx.map +1 -1
- package/lib/solid/types.d.ts +1 -1
- package/lib/svelte/MasonryItem.svelte +49 -0
- package/lib/svelte/VGrid.svelte +1 -1
- package/lib/svelte/VList.svelte +1 -1
- package/lib/svelte/VMasonry.svelte +196 -0
- package/lib/svelte/VMasonry.type.d.ts +108 -0
- package/lib/svelte/Virtualizer.svelte +1 -1
- package/lib/svelte/Virtualizer.type.d.ts +3 -1
- package/lib/svelte/WindowVirtualizer.svelte +1 -1
- package/lib/svelte/WindowVirtualizer.type.d.ts +3 -1
- package/lib/svelte/index.d.ts +2 -0
- package/lib/svelte/index.js +2 -1
- package/lib/svelte/types.d.ts +2 -2
- package/lib/vue/VMasonry.d.ts +114 -0
- package/lib/vue/Virtualizer.d.ts +3 -1
- package/lib/vue/WindowVirtualizer.d.ts +3 -1
- package/lib/vue/index.cjs +632 -403
- package/lib/vue/index.cjs.map +1 -1
- package/lib/vue/index.d.ts +2 -0
- package/lib/vue/index.js +679 -455
- package/lib/vue/index.js.map +1 -1
- package/package.json +6 -16
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
   [](https://bestofjs.org/projects/virtua) [](https://deepwiki.com/inokawa/virtua) [](https://github.com/inokawa/virtua/actions/workflows/check.yml) [](https://github.com/inokawa/virtua/actions/workflows/demo.yml)
|
|
4
4
|
|
|
5
|
-
> A zero-config, fast and small virtual list and
|
|
5
|
+
> A zero-config, fast and small virtual list, grid and masonry component for [React](https://github.com/facebook/react), [Vue](https://vuejs.org/), [Solid](https://www.solidjs.com/), [Svelte](https://svelte.dev/) and [Angular](https://angular.dev/).
|
|
6
6
|
|
|
7
7
|
 
|
|
8
8
|
|
|
@@ -15,7 +15,7 @@ This project is a challenge to rethink virtualization. The goals are...
|
|
|
15
15
|
- **Zero-config virtualization:** This library is designed to give the best performance without configuration. It also handles common hard things in the real world (dynamic size measurement, scroll position adjustment while reverse scrolling and imperative scrolling, iOS support, etc).
|
|
16
16
|
- **Fast:** Natural virtual scrolling needs optimization in many aspects (eliminate frame drops by reducing CPU usage and GC, reduce [synchronous layout recalculation](https://gist.github.com/paulirish/5d52fb081b3570c81e3a), reduce visual jumps on repaint, optimize with CSS, optimize for JIT, optimize for frameworks, etc). We are trying to combine the best of them.
|
|
17
17
|
- **Small:** Its bundle size should be small as much as possible to be friendly with modern web development. Currently components start from ~3kB gzipped and are tree-shakeable.
|
|
18
|
-
- **Flexible:** Aiming to support many usecases - fixed size, dynamic size, horizontal scrolling, reverse scrolling, table, RTL, mobile, infinite scrolling, scroll restoration, DnD, keyboard navigation, sticky and more. See [live demo](#demo).
|
|
18
|
+
- **Flexible:** Aiming to support many usecases - fixed size, dynamic size, horizontal scrolling, reverse scrolling, table, masonry, RTL, mobile, infinite scrolling, scroll restoration, DnD, keyboard navigation, sticky and more. See [live demo](#demo).
|
|
19
19
|
- **Framework agnostic:** [React](https://react.dev/), [Vue](https://vuejs.org/), [Solid](https://www.solidjs.com/), [Svelte](https://svelte.dev/) and [Angular](https://angular.dev/) are supported. We could support other frameworks in the future.
|
|
20
20
|
|
|
21
21
|
## Demo
|
|
@@ -33,7 +33,7 @@ If you want to support legacy browsers, you need polyfills.
|
|
|
33
33
|
- [ResizeObserver](https://caniuse.com/?search=resizeobserver) is always required (e.g. [@juggle/resize-observer](https://github.com/juggle/resize-observer#switching-between-native-and-polyfilled-versions)).
|
|
34
34
|
- [Scroll methods on elements](https://caniuse.com/element-scroll-methods) are always required (e.g. [element-scroll-polyfill](https://github.com/idmadj/element-scroll-polyfill)).
|
|
35
35
|
- [CSS scroll-behavior](https://caniuse.com/?search=scroll-behavior) is required only if you call `scrollToIndex` with `smooth: true` (e.g. [scroll-behavior-polyfill](https://github.com/wessberg/scroll-behavior-polyfill)).
|
|
36
|
-
- [CSS inset-inline-start](https://caniuse.com/mdn-css_properties_inset-inline-start) is required only if you set `horizontal: true`. It cannot be polyfilled so use `virtua@<=0.50` instead.
|
|
36
|
+
- [CSS inset-inline-start](https://caniuse.com/mdn-css_properties_inset-inline-start) is required only if you set `horizontal: true` or use `VMasonry`. It cannot be polyfilled so use `virtua@<=0.50` instead.
|
|
37
37
|
- [CSS subgrid](https://caniuse.com/css-subgrid) is required for VGrid.
|
|
38
38
|
|
|
39
39
|
## Getting started
|
|
@@ -44,48 +44,93 @@ If you want to support legacy browsers, you need polyfills.
|
|
|
44
44
|
|
|
45
45
|
If you use ESM and webpack 5, use react >= 18 to avoid [Can't resolve `react/jsx-runtime` error](https://github.com/facebook/react/issues/20235).
|
|
46
46
|
|
|
47
|
-
#### Vertical scroll
|
|
48
|
-
|
|
49
47
|
```tsx
|
|
50
48
|
import { VList } from "virtua";
|
|
51
49
|
|
|
52
|
-
|
|
50
|
+
const sizes = [20, 40, 80, 77];
|
|
51
|
+
const data = Array.from({ length: 1000 }).map((_, i) => ({
|
|
52
|
+
id: i,
|
|
53
|
+
size: sizes[i % 4],
|
|
54
|
+
}));
|
|
55
|
+
|
|
53
56
|
export const App = () => {
|
|
54
57
|
return (
|
|
55
|
-
<VList style={{ height: 800 }}>
|
|
56
|
-
{
|
|
58
|
+
<VList data={data} style={{ height: 800 }}>
|
|
59
|
+
{(item) => (
|
|
57
60
|
<div
|
|
58
|
-
key={
|
|
61
|
+
key={item.id}
|
|
59
62
|
style={{
|
|
60
|
-
height:
|
|
61
|
-
borderBottom: "solid 1px gray",
|
|
63
|
+
height: item.size,
|
|
62
64
|
background: "white",
|
|
65
|
+
borderBottom: "solid 1px #ccc",
|
|
63
66
|
}}
|
|
64
67
|
>
|
|
65
|
-
{
|
|
68
|
+
{item.id}
|
|
66
69
|
</div>
|
|
67
|
-
)
|
|
70
|
+
)}
|
|
68
71
|
</VList>
|
|
69
72
|
);
|
|
70
73
|
};
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
You can also pass elements as `children` directly instead of `data`.
|
|
77
|
+
|
|
78
|
+
### Vue
|
|
79
|
+
|
|
80
|
+
`vue >= 3.2` is required.
|
|
81
|
+
|
|
82
|
+
```vue
|
|
83
|
+
<script setup>
|
|
84
|
+
import { VList } from "virtua/vue";
|
|
85
|
+
|
|
86
|
+
const sizes = [20, 40, 80, 77];
|
|
87
|
+
const data = Array.from({ length: 1000 }).map((_, i) => ({
|
|
88
|
+
id: i,
|
|
89
|
+
size: sizes[i % 4],
|
|
90
|
+
}));
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
<template>
|
|
94
|
+
<VList :data="data" :style="{ height: '800px' }" #default="{ item }">
|
|
95
|
+
<div
|
|
96
|
+
:key="item.id"
|
|
97
|
+
:style="{
|
|
98
|
+
height: item.size + 'px',
|
|
99
|
+
background: 'white',
|
|
100
|
+
borderBottom: 'solid 1px #ccc',
|
|
101
|
+
}"
|
|
102
|
+
>
|
|
103
|
+
{{ item.id }}
|
|
104
|
+
</div>
|
|
105
|
+
</VList>
|
|
106
|
+
</template>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Solid
|
|
110
|
+
|
|
111
|
+
`solid-js >= 1.0` is required.
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
import { VList } from "virtua/solid";
|
|
115
|
+
|
|
116
|
+
const sizes = [20, 40, 80, 77];
|
|
117
|
+
const data = Array.from({ length: 1000 }).map((_, i) => ({
|
|
118
|
+
id: i,
|
|
119
|
+
size: sizes[i % 4],
|
|
120
|
+
}));
|
|
71
121
|
|
|
72
|
-
// or render prop
|
|
73
122
|
export const App = () => {
|
|
74
|
-
const items = Array.from({ length: 1000 }).map(
|
|
75
|
-
() => Math.floor(Math.random() * 10) * 10 + 10,
|
|
76
|
-
);
|
|
77
123
|
return (
|
|
78
|
-
<VList data={
|
|
79
|
-
{(
|
|
124
|
+
<VList data={data} style={{ height: "800px" }}>
|
|
125
|
+
{(item) => (
|
|
80
126
|
<div
|
|
81
|
-
key={i}
|
|
82
127
|
style={{
|
|
83
|
-
height:
|
|
84
|
-
borderBottom: "solid 1px gray",
|
|
128
|
+
height: item.size + "px",
|
|
85
129
|
background: "white",
|
|
130
|
+
"border-bottom": "solid 1px #ccc",
|
|
86
131
|
}}
|
|
87
132
|
>
|
|
88
|
-
{
|
|
133
|
+
{item.id}
|
|
89
134
|
</div>
|
|
90
135
|
)}
|
|
91
136
|
</VList>
|
|
@@ -93,89 +138,191 @@ export const App = () => {
|
|
|
93
138
|
};
|
|
94
139
|
```
|
|
95
140
|
|
|
96
|
-
|
|
141
|
+
### Svelte
|
|
142
|
+
|
|
143
|
+
`svelte >= 5.0` is required.
|
|
144
|
+
|
|
145
|
+
```svelte
|
|
146
|
+
<script lang="ts">
|
|
147
|
+
import { VList } from "virtua/svelte";
|
|
148
|
+
|
|
149
|
+
const sizes = [20, 40, 80, 77];
|
|
150
|
+
const data = Array.from({ length: 1000 }).map((_, i) => ({
|
|
151
|
+
id: i,
|
|
152
|
+
size: sizes[i % 4],
|
|
153
|
+
}));
|
|
154
|
+
</script>
|
|
155
|
+
|
|
156
|
+
<VList {data} style="height: 800px;" getKey={(item) => item.id}>
|
|
157
|
+
{#snippet children(item)}
|
|
158
|
+
<div
|
|
159
|
+
style="
|
|
160
|
+
height: {item.size}px;
|
|
161
|
+
background: white;
|
|
162
|
+
border-bottom: solid 1px #ccc;
|
|
163
|
+
"
|
|
164
|
+
>
|
|
165
|
+
{item.id}
|
|
166
|
+
</div>
|
|
167
|
+
{/snippet}
|
|
168
|
+
</VList>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Angular
|
|
172
|
+
|
|
173
|
+
`@angular/core >= 20` is required. The components are zoneless-ready and work with both zone.js and [`provideZonelessChangeDetection`](https://angular.dev/api/core/provideZonelessChangeDetection).
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
import { Component } from "@angular/core";
|
|
177
|
+
import { VList } from "virtua/angular";
|
|
178
|
+
|
|
179
|
+
const sizes = [20, 40, 80, 77];
|
|
180
|
+
|
|
181
|
+
@Component({
|
|
182
|
+
selector: "app-root",
|
|
183
|
+
imports: [VList],
|
|
184
|
+
template: `
|
|
185
|
+
<virtua-vlist [data]="data" [getKey]="getKey" style="height: 800px;">
|
|
186
|
+
<ng-template let-item>
|
|
187
|
+
<div
|
|
188
|
+
[style.height.px]="item.size"
|
|
189
|
+
style="background: white; border-bottom: solid 1px #ccc;"
|
|
190
|
+
>
|
|
191
|
+
{{ item.id }}
|
|
192
|
+
</div>
|
|
193
|
+
</ng-template>
|
|
194
|
+
</virtua-vlist>
|
|
195
|
+
`,
|
|
196
|
+
})
|
|
197
|
+
export class App {
|
|
198
|
+
protected readonly data = Array.from({ length: 1000 }).map((_, i) => ({
|
|
199
|
+
id: i,
|
|
200
|
+
size: sizes[i % 4],
|
|
201
|
+
}));
|
|
202
|
+
protected readonly getKey = (item: { id: number }) => item.id;
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Other bindings
|
|
207
|
+
|
|
208
|
+
- [vanilla-virtua](https://github.com/aabccd021/vanilla-virtua): virtua for vanilla js
|
|
209
|
+
|
|
210
|
+
## Usage
|
|
211
|
+
|
|
212
|
+
The examples are written in React, but the same components and props are available in all frameworks.
|
|
213
|
+
|
|
214
|
+
### Horizontal scroll
|
|
97
215
|
|
|
98
216
|
```tsx
|
|
99
217
|
import { VList } from "virtua";
|
|
100
218
|
|
|
219
|
+
const sizes = [20, 40, 80, 77];
|
|
220
|
+
const data = Array.from({ length: 1000 }).map((_, i) => ({
|
|
221
|
+
id: i,
|
|
222
|
+
size: sizes[i % 4],
|
|
223
|
+
}));
|
|
224
|
+
|
|
101
225
|
export const App = () => {
|
|
102
226
|
return (
|
|
103
|
-
<VList style={{ height: 400 }} horizontal>
|
|
104
|
-
{
|
|
227
|
+
<VList data={data} style={{ height: 400 }} horizontal>
|
|
228
|
+
{(item) => (
|
|
105
229
|
<div
|
|
106
|
-
key={
|
|
230
|
+
key={item.id}
|
|
107
231
|
style={{
|
|
108
|
-
width:
|
|
109
|
-
borderRight: "solid 1px gray",
|
|
232
|
+
width: item.size,
|
|
110
233
|
background: "white",
|
|
234
|
+
borderRight: "solid 1px #ccc",
|
|
111
235
|
}}
|
|
112
236
|
>
|
|
113
|
-
{
|
|
237
|
+
{item.id}
|
|
114
238
|
</div>
|
|
115
|
-
)
|
|
239
|
+
)}
|
|
116
240
|
</VList>
|
|
117
241
|
);
|
|
118
242
|
};
|
|
119
243
|
```
|
|
120
244
|
|
|
121
|
-
|
|
245
|
+
### Custom scroll container
|
|
122
246
|
|
|
123
247
|
`VList` is a recommended solution which works like a drop-in replacement of simple list built with scrollable `div` (or removed [virtual-scroller element](https://github.com/WICG/virtual-scroller)). For more complicated styling or markup, use `Virtualizer`.
|
|
124
248
|
|
|
125
249
|
```tsx
|
|
126
250
|
import { Virtualizer } from "virtua";
|
|
127
251
|
|
|
252
|
+
const sizes = [20, 40, 80, 77];
|
|
253
|
+
const data = Array.from({ length: 1000 }).map((_, i) => ({
|
|
254
|
+
id: i,
|
|
255
|
+
size: sizes[i % 4],
|
|
256
|
+
}));
|
|
257
|
+
const headerHeight = 40;
|
|
258
|
+
|
|
128
259
|
export const App = () => {
|
|
129
260
|
return (
|
|
130
|
-
<div
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
261
|
+
<div
|
|
262
|
+
style={{
|
|
263
|
+
height: 800,
|
|
264
|
+
overflowY: "auto",
|
|
265
|
+
// opt out browser's scroll anchoring on header/footer because it will conflict with scroll anchoring of virtualizer
|
|
266
|
+
overflowAnchor: "none",
|
|
267
|
+
}}
|
|
268
|
+
>
|
|
269
|
+
<div style={{ height: headerHeight }}>header</div>
|
|
270
|
+
<Virtualizer data={data} startMargin={headerHeight}>
|
|
271
|
+
{(item) => (
|
|
134
272
|
<div
|
|
135
|
-
key={
|
|
273
|
+
key={item.id}
|
|
136
274
|
style={{
|
|
137
|
-
height:
|
|
138
|
-
borderBottom: "solid 1px gray",
|
|
275
|
+
height: item.size,
|
|
139
276
|
background: "white",
|
|
277
|
+
borderBottom: "solid 1px #ccc",
|
|
140
278
|
}}
|
|
141
279
|
>
|
|
142
|
-
{
|
|
280
|
+
{item.id}
|
|
143
281
|
</div>
|
|
144
|
-
)
|
|
282
|
+
)}
|
|
145
283
|
</Virtualizer>
|
|
284
|
+
<div style={{ height: 600 }}>footer</div>
|
|
146
285
|
</div>
|
|
147
286
|
);
|
|
148
287
|
};
|
|
149
288
|
```
|
|
150
289
|
|
|
151
|
-
|
|
290
|
+
### Window scroll
|
|
152
291
|
|
|
153
292
|
```tsx
|
|
154
293
|
import { WindowVirtualizer } from "virtua";
|
|
155
294
|
|
|
295
|
+
const sizes = [20, 40, 80, 77];
|
|
296
|
+
const data = Array.from({ length: 1000 }).map((_, i) => ({
|
|
297
|
+
id: i,
|
|
298
|
+
size: sizes[i % 4],
|
|
299
|
+
}));
|
|
300
|
+
|
|
156
301
|
export const App = () => {
|
|
157
302
|
return (
|
|
158
303
|
<div style={{ padding: 200 }}>
|
|
159
|
-
<WindowVirtualizer>
|
|
160
|
-
{
|
|
304
|
+
<WindowVirtualizer data={data}>
|
|
305
|
+
{(item) => (
|
|
161
306
|
<div
|
|
162
|
-
key={
|
|
307
|
+
key={item.id}
|
|
163
308
|
style={{
|
|
164
|
-
height:
|
|
165
|
-
borderBottom: "solid 1px gray",
|
|
309
|
+
height: item.size,
|
|
166
310
|
background: "white",
|
|
311
|
+
borderBottom: "solid 1px #ccc",
|
|
167
312
|
}}
|
|
168
313
|
>
|
|
169
|
-
{
|
|
314
|
+
{item.id}
|
|
170
315
|
</div>
|
|
171
|
-
)
|
|
316
|
+
)}
|
|
172
317
|
</WindowVirtualizer>
|
|
173
318
|
</div>
|
|
174
319
|
);
|
|
175
320
|
};
|
|
176
321
|
```
|
|
177
322
|
|
|
178
|
-
|
|
323
|
+
### Tabular data
|
|
324
|
+
|
|
325
|
+
For data with rows and columns such as a table, use `VGrid`. It virtualizes on both axes.
|
|
179
326
|
|
|
180
327
|
```tsx
|
|
181
328
|
import { VGrid } from "virtua";
|
|
@@ -189,32 +336,38 @@ const columns = [
|
|
|
189
336
|
{ key: "bio", width: 480 },
|
|
190
337
|
] as const;
|
|
191
338
|
|
|
192
|
-
const rows =
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
339
|
+
const rows = [
|
|
340
|
+
// the header row has no data
|
|
341
|
+
null,
|
|
342
|
+
...Array.from({ length: 10000 }).map((_, i) => ({
|
|
343
|
+
id: i,
|
|
344
|
+
name: `User ${i}`,
|
|
345
|
+
email: `user${i}@example.com`,
|
|
346
|
+
age: 20 + (i % 50),
|
|
347
|
+
joined: new Date(2020, 0, 1 + i).toDateString(),
|
|
348
|
+
bio: `Hello, I'm user ${i}.`,
|
|
349
|
+
})),
|
|
350
|
+
];
|
|
200
351
|
|
|
201
352
|
export const App = () => {
|
|
202
353
|
return (
|
|
203
354
|
<VGrid
|
|
204
|
-
style={{ height: 800, border: "solid 1px
|
|
355
|
+
style={{ height: 800, border: "solid 1px #ccc", background: "white" }}
|
|
205
356
|
rows={rows}
|
|
206
357
|
rowHeight={40}
|
|
207
358
|
cols={columns}
|
|
208
359
|
colWidth="width"
|
|
360
|
+
headerRows={1}
|
|
209
361
|
>
|
|
210
362
|
{(row, col) => (
|
|
211
363
|
<div
|
|
212
364
|
style={{
|
|
213
|
-
borderRight: "solid 1px
|
|
214
|
-
borderBottom: "solid 1px
|
|
365
|
+
borderRight: "solid 1px #ccc",
|
|
366
|
+
borderBottom: "solid 1px #ccc",
|
|
367
|
+
background: row === null ? "lightgray" : undefined,
|
|
215
368
|
}}
|
|
216
369
|
>
|
|
217
|
-
{row[col.key]}
|
|
370
|
+
{row === null ? col.key : row[col.key]}
|
|
218
371
|
</div>
|
|
219
372
|
)}
|
|
220
373
|
</VGrid>
|
|
@@ -222,133 +375,47 @@ export const App = () => {
|
|
|
222
375
|
};
|
|
223
376
|
```
|
|
224
377
|
|
|
225
|
-
###
|
|
226
|
-
|
|
227
|
-
`vue >= 3.2` is required.
|
|
378
|
+
### Masonry
|
|
228
379
|
|
|
229
|
-
|
|
230
|
-
<script setup>
|
|
231
|
-
import { VList } from "virtua/vue";
|
|
232
|
-
|
|
233
|
-
const sizes = [20, 40, 180, 77];
|
|
234
|
-
const data = Array.from({ length: 1000 }).map((_, i) => sizes[i % 4]);
|
|
235
|
-
</script>
|
|
236
|
-
|
|
237
|
-
<template>
|
|
238
|
-
<VList :data="data" :style="{ height: '800px' }" #default="{ item, index }">
|
|
239
|
-
<div
|
|
240
|
-
:key="index"
|
|
241
|
-
:style="{
|
|
242
|
-
height: item + 'px',
|
|
243
|
-
borderBottom: 'solid 1px #ccc',
|
|
244
|
-
background: 'white',
|
|
245
|
-
}"
|
|
246
|
-
>
|
|
247
|
-
{{ index }}
|
|
248
|
-
</div>
|
|
249
|
-
</VList>
|
|
250
|
-
</template>
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
### Solid
|
|
254
|
-
|
|
255
|
-
`solid-js >= 1.0` is required.
|
|
380
|
+
For items with different heights laid out in columns such as a gallery, use `VMasonry`.
|
|
256
381
|
|
|
257
382
|
```tsx
|
|
258
|
-
import {
|
|
383
|
+
import { VMasonry } from "virtua";
|
|
259
384
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
385
|
+
const sizes = [100, 180, 140, 220];
|
|
386
|
+
const colors = ["skyblue", "pink", "khaki", "lightgreen", "plum"];
|
|
387
|
+
const data = Array.from({ length: 1000 }).map((_, i) => ({
|
|
388
|
+
id: i,
|
|
389
|
+
size: sizes[i % 4],
|
|
390
|
+
color: colors[i % 5],
|
|
391
|
+
}));
|
|
263
392
|
|
|
393
|
+
export const App = () => {
|
|
264
394
|
return (
|
|
265
|
-
<
|
|
266
|
-
{(
|
|
395
|
+
<VMasonry data={data} lanes={3} gap={8} style={{ height: 800 }}>
|
|
396
|
+
{(item) => (
|
|
267
397
|
<div
|
|
398
|
+
key={item.id}
|
|
268
399
|
style={{
|
|
269
|
-
height:
|
|
270
|
-
|
|
271
|
-
background: "white",
|
|
400
|
+
height: item.size,
|
|
401
|
+
background: item.color,
|
|
272
402
|
}}
|
|
273
403
|
>
|
|
274
|
-
{
|
|
404
|
+
{item.id}
|
|
275
405
|
</div>
|
|
276
406
|
)}
|
|
277
|
-
</
|
|
407
|
+
</VMasonry>
|
|
278
408
|
);
|
|
279
409
|
};
|
|
280
410
|
```
|
|
281
411
|
|
|
282
|
-
###
|
|
283
|
-
|
|
284
|
-
`svelte >= 5.0` is required.
|
|
285
|
-
|
|
286
|
-
```svelte
|
|
287
|
-
<script lang="ts">
|
|
288
|
-
import { VList } from "virtua/svelte";
|
|
412
|
+
### More examples
|
|
289
413
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const data = Array.from({ length: 1000 }).map((_, i) => sizes[i % 4]);
|
|
293
|
-
</script>
|
|
294
|
-
|
|
295
|
-
<VList {data} style="height: 800px;" getKey={(_, i) => i}>
|
|
296
|
-
{#snippet children(item, index)}
|
|
297
|
-
<div
|
|
298
|
-
style="
|
|
299
|
-
height: {item}px;
|
|
300
|
-
border-bottom: solid 1px #ccc;
|
|
301
|
-
background: white;
|
|
302
|
-
"
|
|
303
|
-
>
|
|
304
|
-
{index}
|
|
305
|
-
</div>
|
|
306
|
-
{/snippet}
|
|
307
|
-
</VList>
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
### Angular
|
|
311
|
-
|
|
312
|
-
`@angular/core >= 20` is required. The components are zoneless-ready and work with both zone.js and [`provideZonelessChangeDetection`](https://angular.dev/api/core/provideZonelessChangeDetection).
|
|
313
|
-
|
|
314
|
-
```ts
|
|
315
|
-
import { Component } from "@angular/core";
|
|
316
|
-
import { VList } from "virtua/angular";
|
|
317
|
-
|
|
318
|
-
const sizes = [20, 40, 180, 77];
|
|
319
|
-
|
|
320
|
-
@Component({
|
|
321
|
-
selector: "app-root",
|
|
322
|
-
imports: [VList],
|
|
323
|
-
template: `
|
|
324
|
-
<virtua-vlist [data]="data" [getKey]="getKey" style="height: 800px;">
|
|
325
|
-
<ng-template let-item let-index="index">
|
|
326
|
-
<div
|
|
327
|
-
[style.height.px]="item"
|
|
328
|
-
style="background: white; border-bottom: solid 1px #ccc;"
|
|
329
|
-
>
|
|
330
|
-
{{ index }}
|
|
331
|
-
</div>
|
|
332
|
-
</ng-template>
|
|
333
|
-
</virtua-vlist>
|
|
334
|
-
`,
|
|
335
|
-
})
|
|
336
|
-
export class App {
|
|
337
|
-
protected readonly data = Array.from({ length: 1000 }).map(
|
|
338
|
-
(_, i) => sizes[i % 4]!,
|
|
339
|
-
);
|
|
340
|
-
protected readonly getKey = (_: number, i: number) => i;
|
|
341
|
-
}
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
### Other bindings
|
|
345
|
-
|
|
346
|
-
- [vanilla-virtua](https://github.com/aabccd021/vanilla-virtua): virtua for vanilla js
|
|
414
|
+
See [demo](https://inokawa.github.io/virtua/) and [its source code](./stories).
|
|
347
415
|
|
|
348
416
|
## Documentation
|
|
349
417
|
|
|
350
418
|
- [API reference](./docs/API.md)
|
|
351
|
-
- [Storybook examples](./stories) for more usages
|
|
352
419
|
- [DeepWiki](https://deepwiki.com/inokawa/virtua)
|
|
353
420
|
|
|
354
421
|
### FAQs
|
|
@@ -408,11 +475,17 @@ It may be dispatched by ResizeObserver in this lib [as described in spec](https:
|
|
|
408
475
|
|
|
409
476
|
Especially for `webpack-dev-server`, [you can filter out the specific error with `devServer.client.overlay.runtimeErrors` option](https://webpack.js.org/configuration/dev-server/#overlay).
|
|
410
477
|
|
|
411
|
-
#### Why my items
|
|
478
|
+
#### Why are my items squashed, overlapped or rendered inconsistently on resize/add/remove/reorder?
|
|
412
479
|
|
|
413
|
-
|
|
480
|
+
Check that each item has a unique key, such as the id of your data, not its index.
|
|
414
481
|
|
|
415
|
-
|
|
482
|
+
- React: `key` of the element of each item
|
|
483
|
+
- Vue: `key` of the root element in the default slot
|
|
484
|
+
- Solid: the item of `data` itself, so keep the same reference for the same item
|
|
485
|
+
- Svelte: `getKey` prop
|
|
486
|
+
- Angular: `getKey` input
|
|
487
|
+
|
|
488
|
+
If it still happens, the `shift` prop may be misused.
|
|
416
489
|
|
|
417
490
|
#### Why `VListHandle.viewportSize` is 0 on mount?
|
|
418
491
|
|
|
@@ -426,32 +499,52 @@ This package uses [exports of package.json](https://nodejs.org/api/packages.html
|
|
|
426
499
|
|
|
427
500
|
### Features
|
|
428
501
|
|
|
429
|
-
| | [virtua](https://github.com/inokawa/virtua) | [react-virtuoso](https://github.com/petyosi/react-virtuoso) | [react-window](https://github.com/bvaughn/react-window) | [react-
|
|
430
|
-
| :------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------- |
|
|
431
|
-
| Bundle size | [](https://bundlephobia.com/package/virtua) | [](https://bundlephobia.com/package/react-virtuoso) | [](https://bundlephobia.com/package/react-window) | [ | ✅ (VGrid) | ❌ | ✅ ([Grid](https://react-window.vercel.app/grid/grid)) | ✅ ([Grid](https://github.com/bvaughn/react-virtualized/blob/master/docs/Grid.md)) |
|
|
436
|
-
| Tabular data (Columns with headers) | ✅ (VGrid) | ✅ (TableVirtuoso) | ✅ ([Supported](https://react-window.vercel.app/list/tabular-data)) | ✅ ([Table](https://github.com/bvaughn/react-virtualized/blob/master/docs/Table.md)) |
|
|
437
|
-
| HTML table element | 🟠 (needs customization) | ✅ (TableVirtuoso) | ❌ |
|
|
438
|
-
| Masonry |
|
|
439
|
-
| Window scroller | ✅ (WindowVirtualizer) | ✅ | ❌ | ✅ ([WindowScroller](https://github.com/bvaughn/react-virtualized/blob/master/docs/WindowScroller.md)) |
|
|
440
|
-
| Dynamic list size | ✅ | ✅ | ✅ | 🟠 (needs [AutoSizer](https://github.com/bvaughn/react-virtualized/blob/master/docs/AutoSizer.md)) |
|
|
441
|
-
| Dynamic item size | ✅ | ✅ |
|
|
442
|
-
| Reverse scroll | ✅ | ✅ | ❌ |
|
|
443
|
-
| Reverse scroll in iOS Safari | 🟠 ([user must release scroll](https://github.com/inokawa/virtua/issues/473)) | 🟠 ([has glitch with unknown sized items](https://github.com/petyosi/react-virtuoso/issues/945)) | ❌ |
|
|
444
|
-
| Infinite scroll | ✅ | ✅ | 🟠 (needs [react-window-infinite-loader](https://github.com/bvaughn/react-window-infinite-loader)) | 🟠 (needs [InfiniteLoader](https://github.com/bvaughn/react-virtualized/blob/master/docs/InfiniteLoader.md)) |
|
|
445
|
-
| Reverse (bi-directional) infinite scroll | ✅ | ✅ | ❌ |
|
|
446
|
-
| Scroll restoration | ✅ | ✅ (getState) | ❌ |
|
|
447
|
-
| Smooth scroll | ✅ | ✅ |
|
|
448
|
-
| SSR support | ✅
|
|
449
|
-
| Render React Server Components (RSC) as children | ✅ | ❌ | ❌ |
|
|
450
|
-
| Display exceeding [browser's max element size](https://stackoverflow.com/questions/10882769/do-the-browsers-have-a-maximum-height-for-the-body-document) limit | ❌ | ❌ | ❌ | ✅ |
|
|
502
|
+
| | [virtua](https://github.com/inokawa/virtua) | [react-virtuoso](https://github.com/petyosi/react-virtuoso) | [react-window](https://github.com/bvaughn/react-window) | [@tanstack/react-virtual](https://github.com/TanStack/virtual) | [react-virtualized](https://github.com/bvaughn/react-virtualized) |
|
|
503
|
+
| :------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
504
|
+
| Bundle size | [](https://bundlephobia.com/package/virtua) | [](https://bundlephobia.com/package/react-virtuoso) | [](https://bundlephobia.com/package/react-window) | [](https://bundlephobia.com/package/@tanstack/react-virtual) | [](https://bundlephobia.com/package/react-virtualized) |
|
|
505
|
+
| Vertical scroll | ✅ | ✅ | ✅ | 🟠 (needs customization) | ✅ |
|
|
506
|
+
| Horizontal scroll | ✅ | ✅ | ✅ | 🟠 (needs customization) | ✅ |
|
|
507
|
+
| Horizontal scroll in RTL direction | ✅ | ✅ | ✅ | 🟠 (isRtl) | ❌ |
|
|
508
|
+
| Grid (Virtualization for two dimensions) | ✅ (VGrid) | ❌ | ✅ ([Grid](https://react-window.vercel.app/grid/grid)) | 🟠 (needs customization) | ✅ ([Grid](https://github.com/bvaughn/react-virtualized/blob/master/docs/Grid.md)) |
|
|
509
|
+
| Tabular data (Columns with headers) | ✅ (VGrid) | ✅ (TableVirtuoso) | ✅ ([Supported](https://react-window.vercel.app/list/tabular-data)) | 🟠 (needs customization) | ✅ ([Table](https://github.com/bvaughn/react-virtualized/blob/master/docs/Table.md)) |
|
|
510
|
+
| HTML table element | 🟠 (needs customization) | ✅ (TableVirtuoso) | ❌ | 🟠 (needs customization) | ❌ |
|
|
511
|
+
| Masonry | ✅ (VMasonry) | ✅ (VirtuosoMasonry) | ❌ | 🟠 (lanes) | ✅ ([Masonry](https://github.com/bvaughn/react-virtualized/blob/master/docs/Masonry.md)) |
|
|
512
|
+
| Window scroller | ✅ (WindowVirtualizer) | ✅ | ❌ | ✅ (useWindowVirtualizer) | ✅ ([WindowScroller](https://github.com/bvaughn/react-virtualized/blob/master/docs/WindowScroller.md)) |
|
|
513
|
+
| Dynamic list size | ✅ | ✅ | ✅ | ✅ | 🟠 (needs [AutoSizer](https://github.com/bvaughn/react-virtualized/blob/master/docs/AutoSizer.md)) |
|
|
514
|
+
| Dynamic item size | ✅ | ✅ | ✅ (useDynamicRowHeight) | ✅ (measureElement) | 🟠 (needs [CellMeasurer](https://github.com/bvaughn/react-virtualized/blob/master/docs/CellMeasurer.md) and has wrong destination when scrolling to item imperatively) |
|
|
515
|
+
| Reverse scroll | ✅ | ✅ | ❌ | ✅ (anchorTo) | ❌ |
|
|
516
|
+
| Reverse scroll in iOS Safari | 🟠 ([user must release scroll](https://github.com/inokawa/virtua/issues/473)) | 🟠 ([has glitch with unknown sized items](https://github.com/petyosi/react-virtuoso/issues/945)) | ❌ | 🟠 | ❌ |
|
|
517
|
+
| Infinite scroll | ✅ | ✅ | 🟠 (needs [react-window-infinite-loader](https://github.com/bvaughn/react-window-infinite-loader)) | ✅ | 🟠 (needs [InfiniteLoader](https://github.com/bvaughn/react-virtualized/blob/master/docs/InfiniteLoader.md)) |
|
|
518
|
+
| Reverse (bi-directional) infinite scroll | ✅ | ✅ | ❌ | ✅ (anchorTo) | ❌ |
|
|
519
|
+
| Scroll restoration | ✅ | ✅ (getState) | ❌ | ✅ (takeSnapshot) | ❌ |
|
|
520
|
+
| Smooth scroll | ✅ | ✅ | 🟠 ([not with useDynamicRowHeight](https://react-window.vercel.app/list/dynamic-row-height)) | ✅ | ❌ |
|
|
521
|
+
| SSR support | ✅ (ssrCount) | ✅ (initialItemCount) | ✅ (defaultHeight) | ✅ (initialRect) | ✅ |
|
|
522
|
+
| Render React Server Components (RSC) as children | ✅ | ❌ | ❌ | 🟠 (needs customization) | ❌ |
|
|
523
|
+
| Display exceeding [browser's max element size](https://stackoverflow.com/questions/10882769/do-the-browsers-have-a-maximum-height-for-the-body-document) limit | ❌ | ❌ | ❌ | ❌ | ✅ |
|
|
524
|
+
|
|
525
|
+
- ✅ - Built-in supported
|
|
526
|
+
- 🟠 - Supported but partial, limited or requires some user custom code
|
|
527
|
+
- ❌ - Not officially supported
|
|
528
|
+
|
|
529
|
+
### Grid features
|
|
530
|
+
|
|
531
|
+
| | [virtua](https://github.com/inokawa/virtua) (VGrid) | [AG Grid Community](https://www.ag-grid.com/react-data-grid/) | [MUI X Data Grid](https://mui.com/x/react-data-grid/) | [react-data-grid](https://github.com/Comcast/react-data-grid) | [@virtuoso.dev/data-table](https://virtuoso.dev/data-table/) | [react-window](https://github.com/bvaughn/react-window) (Grid) | [@tanstack/react-virtual](https://github.com/TanStack/virtual) |
|
|
532
|
+
| :------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
533
|
+
| Bundle size | [](https://bundlephobia.com/package/virtua) | [](https://bundlephobia.com/package/ag-grid-react) | [](https://bundlephobia.com/package/@mui/x-data-grid) | [](https://bundlephobia.com/package/react-data-grid) | [](https://bundlephobia.com/package/@virtuoso.dev/data-table) | [](https://bundlephobia.com/package/react-window) | [](https://bundlephobia.com/package/@tanstack/react-virtual) |
|
|
534
|
+
| Row / column virtualization | ✅ | ✅ | 🟠 (rows are limited to 100 per page, 💰 [Pro](https://mui.com/x/react-data-grid/virtualization/#row-virtualization) for more) | ✅ | ✅ | ✅ | 🟠 (needs customization) |
|
|
535
|
+
| Dynamic row height / column width | ✅ (auto) | ✅ ([autoHeight](https://www.ag-grid.com/react-data-grid/row-height/#auto-row-height) / [autoSizeStrategy](https://www.ag-grid.com/react-data-grid/column-sizing/#continuous-auto-sizing)) | 🟠 (only row height, column width needs [autosizing on demand](https://mui.com/x/react-data-grid/column-dimensions/#autosizing)) | 🟠 (only column width, measured once) | 🟠 (only row height, column width is [measured from header cells](https://virtuoso.dev/data-table/columns/column-layout/)) | ❌ | 🟠 (measureElement) |
|
|
536
|
+
| Pinned rows / columns | ✅ | ✅ | 💰 (Pro, [rows](https://mui.com/x/react-data-grid/row-pinning/) / [columns](https://mui.com/x/react-data-grid/column-pinning/)) | ✅ | 🟠 (only columns) | ❌ | 🟠 (rangeExtractor) |
|
|
537
|
+
| Row / column spanning | ✅ | ✅ | 🟠 ([row spanning needs fixed row height](https://mui.com/x/react-data-grid/row-spanning/)) | 🟠 (only columns) | 🟠 (only [column group headers](https://virtuoso.dev/data-table/columns/column-groups/)) | ❌ | ❌ |
|
|
538
|
+
| Sticky group rows | ✅ (sectionRows) | 💰 ([Enterprise](https://www.ag-grid.com/react-data-grid/grouping-opening-groups/)) | ❌ | ❌ | ✅ ([Grouped rows](https://virtuoso.dev/data-table/grouped-rows/)) | ❌ | 🟠 (rangeExtractor) |
|
|
539
|
+
| SSR support | ❌ | ❌ | ❌ | 🟠 (limited to the first few rows) | ❌ | ✅ (defaultHeight, defaultWidth) | ✅ (initialRect) |
|
|
540
|
+
| Window scroller | ❌ | ❌ | ❌ | ❌ | ✅ (useWindowScroll) | ❌ | ✅ (useWindowVirtualizer) |
|
|
541
|
+
| Display exceeding [browser's max element size](https://stackoverflow.com/questions/10882769/do-the-browsers-have-a-maximum-height-for-the-body-document) limit | ❌ | ✅ ([Stretching](https://www.ag-grid.com/react-data-grid/massive-row-count/)) | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
542
|
+
| Use with [TanStack Table](https://github.com/TanStack/table) | ✅ ([example](./stories/react/advanced/With%20tanstack-table.stories.tsx)) | ❌ | ❌ | ❌ | ❌ | 🟠 (needs customization) | ✅ ([guide](https://tanstack.com/table/latest/docs/framework/react/guide/virtualization)) |
|
|
451
543
|
|
|
452
544
|
- ✅ - Built-in supported
|
|
453
545
|
- 🟠 - Supported but partial, limited or requires some user custom code
|
|
454
546
|
- ❌ - Not officially supported
|
|
547
|
+
- 💰 - Supported only in paid plans
|
|
455
548
|
|
|
456
549
|
### Benchmark
|
|
457
550
|
|