Gathering detailed insights and metrics for virtua
Gathering detailed insights and metrics for virtua
Gathering detailed insights and metrics for virtua
Gathering detailed insights and metrics for virtua
A zero-config, fast and small (~3kB) virtual list (and grid) component for React, Vue, Solid and Svelte.
npm install virtua
Typescript
Module System
Node Version
NPM Version
82.4
Supply Chain
93.5
Quality
94.2
Maintenance
100
Vulnerability
100
License
TypeScript (91.15%)
Svelte (3.77%)
JavaScript (2.93%)
Vue (1.45%)
CSS (0.68%)
HTML (0.02%)
Total Downloads
2,302,780
Last Day
11,372
Last Week
51,668
Last Month
221,531
Last Year
2,059,799
2,311 Stars
1,397 Commits
60 Forks
6 Watching
14 Branches
17 Contributors
Minified
Minified + Gzipped
Latest Version
0.40.0
Package Id
virtua@0.40.0
Unpacked Size
1.20 MB
Size
278.74 kB
File Count
77
NPM Version
10.7.0
Node Version
22.2.0
Publised On
29 Jan 2025
Cumulative downloads
Total Downloads
Last day
-5.4%
11,372
Compared to previous day
Last week
-14.6%
51,668
Compared to previous week
Last month
2.4%
221,531
Compared to previous month
Last year
748%
2,059,799
Compared to previous year
92
A zero-config, fast and small (~3kB) virtual list (and grid) component for React, Vue, Solid and Svelte.
If you want to check the difference with the alternatives right away, see comparison section.
This project is a challenge to rethink virtualization. The goals are...
https://inokawa.github.io/virtua/
1npm install virtua
If you use this lib in legacy browsers which does not have ResizeObserver, you should use polyfill.
react >= 16.14
is required.
If you use ESM and webpack 5, use react >= 18 to avoid Can't resolve react/jsx-runtime
error.
1import { VList } from "virtua"; 2 3export const App = () => { 4 return ( 5 <VList style={{ height: 800 }}> 6 {Array.from({ length: 1000 }).map((_, i) => ( 7 <div 8 key={i} 9 style={{ 10 height: Math.floor(Math.random() * 10) * 10 + 10, 11 borderBottom: "solid 1px gray", 12 background: "white", 13 }} 14 > 15 {i} 16 </div> 17 ))} 18 </VList> 19 ); 20};
1import { VList } from "virtua"; 2 3export const App = () => { 4 return ( 5 <VList style={{ height: 400 }} horizontal> 6 {Array.from({ length: 1000 }).map((_, i) => ( 7 <div 8 key={i} 9 style={{ 10 width: Math.floor(Math.random() * 10) * 10 + 10, 11 borderRight: "solid 1px gray", 12 background: "white", 13 }} 14 > 15 {i} 16 </div> 17 ))} 18 </VList> 19 ); 20};
VList
is a recommended solution which works like a drop-in replacement of simple list built with scrollable div
(or removed virtual-scroller element). For more complicated styling or markup, use Virtualizer
.
1import { Virtualizer } from "virtua"; 2 3export const App = () => { 4 return ( 5 <div style={{ overflowY: "auto", height: 800 }}> 6 <div style={{ height: 40 }}>header</div> 7 <Virtualizer startMargin={40}> 8 {Array.from({ length: 1000 }).map((_, i) => ( 9 <div 10 key={i} 11 style={{ 12 height: Math.floor(Math.random() * 10) * 10 + 10, 13 borderBottom: "solid 1px gray", 14 background: "white", 15 }} 16 > 17 {i} 18 </div> 19 ))} 20 </Virtualizer> 21 </div> 22 ); 23};
1import { WindowVirtualizer } from "virtua"; 2 3export const App = () => { 4 return ( 5 <div style={{ padding: 200 }}> 6 <WindowVirtualizer> 7 {Array.from({ length: 1000 }).map((_, i) => ( 8 <div 9 key={i} 10 style={{ 11 height: Math.floor(Math.random() * 10) * 10 + 10, 12 borderBottom: "solid 1px gray", 13 background: "white", 14 }} 15 > 16 {i} 17 </div> 18 ))} 19 </WindowVirtualizer> 20 </div> 21 ); 22};
1import { experimental_VGrid as VGrid } from "virtua"; 2 3export const App = () => { 4 return ( 5 <VGrid style={{ height: 800 }} row={1000} col={500}> 6 {({ rowIndex, colIndex }) => ( 7 <div 8 style={{ 9 width: ((colIndex % 3) + 1) * 100, 10 border: "solid 1px gray", 11 background: "white", 12 }} 13 > 14 {rowIndex} / {colIndex} 15 </div> 16 )} 17 </VGrid> 18 ); 19};
This library is marked as a Client Component. You can render RSC as children of VList
, Virtualizer
or WindowVirtualizer
.
1// page.tsx in App Router of Next.js 2 3export default async () => { 4 const articles = await fetchArticles(); 5 return ( 6 <div> 7 <div>This is Server Component</div> 8 <VList style={{ height: 300 }}> 9 {articles.map((a) => ( 10 <div key={a.id} style={{ border: "solid 1px gray", height: 80 }}> 11 {a.content} 12 </div> 13 ))} 14 </VList> 15 </div> 16 ); 17};
vue >= 3.2
is required.
1<script setup> 2import { VList } from "virtua/vue"; 3 4const sizes = [20, 40, 180, 77]; 5const data = Array.from({ length: 1000 }).map((_, i) => sizes[i % 4]); 6</script> 7 8<template> 9 <VList :data="data" :style="{ height: '800px' }" #default="{ item, index }"> 10 <div 11 :key="index" 12 :style="{ 13 height: item + 'px', 14 background: 'white', 15 borderBottom: 'solid 1px #ccc', 16 }" 17 > 18 {{ index }} 19 </div> 20 </VList> 21</template>
solid-js >= 1.0
is required.
1import { VList } from "virtua/solid"; 2 3export const App = () => { 4 const sizes = [20, 40, 80, 77]; 5 const data = Array.from({ length: 1000 }).map((_, i) => sizes[i % 4]); 6 7 return ( 8 <VList data={data} style={{ height: "800px" }}> 9 {(d, i) => ( 10 <div 11 style={{ 12 height: d + "px", 13 "border-bottom": "solid 1px #ccc", 14 background: "#fff", 15 }} 16 > 17 {i} 18 </div> 19 )} 20 </VList> 21 ); 22};
svelte >= 5.0
is required.
1<script lang="ts"> 2 import { VList } from "virtua/svelte"; 3 4 const sizes = [20, 40, 180, 77]; 5 6 const data = Array.from({ length: 1000 }).map((_, i) => sizes[i % 4] ); 7</script> 8 9<VList {data} style="height: 100vh;" getKey={(_, i) => i}> 10 {#snippet children(item, index)} 11 <div 12 style=" 13 height: {item}px; 14 background: white; 15 border-bottom: solid 1px #ccc; 16 " 17 > 18 {index} 19 </div> 20 {/snippet} 21</VList>
In complex usage, especially if you re-render frequently the parent of virtual scroller or the children are tons of items, children element creation can be a performance bottle neck. That's because creating React elements is fast enough but not free and new React element instances break some of memoization inside virtual scroller.
One solution is memoization with useMemo
. You can use it to reduce computation and keep the elements' instance the same. And if you want to pass state from parent to the items, using context
instead of props may be better because it doesn't break the memoization.
1const elements = useMemo( 2 () => tooLongArray.map((d) => <Component key={d.id} {...d} />), 3 [tooLongArray] 4); 5const [position, setPosition] = useState(0); 6return ( 7 <div> 8 <div>position: {position}</div> 9 <VList onScroll={(offset) => setPosition(offset)}>{elements}</VList> 10 </div> 11);
The other solution is using render prop
as children to create elements lazily. It will effectively reduce cost on start up when you render many items (>1000). An important point is that newly created elements from render prop
will disable optimization possible with cached element instances. We recommend using memo
to reduce calling render function of your item components during scrolling.
1const Component = memo(HeavyItem); 2 3<VList count={items.length}> 4 {(i) => { 5 const item = items[i]; 6 return <Component key={item.id} data={item} />; 7 }} 8</VList>;
Decreasing overscan
prop may also improve perf in case that components are large and heavy.
Virtua try to suppress glitch caused by resize as much as possible, but it will also require additional work. If your item contains something resized often, such as lazy loaded image, we recommend to set height or min-height to it if possible.
ResizeObserver loop completed with undelivered notifications.
error?It may be dispatched by ResizeObserver in this lib as described in spec, and this is a common problem with ResizeObserver. If it bothers you, you can safely ignore it.
Especially for webpack-dev-server
, you can filter out the specific error with devServer.client.overlay.runtimeErrors
option.
Maybe you forgot to pass key
prop to each items, or the keys are not unique. Item sizes are stored per key.
VListHandle.viewportSize
is 0 on mount?viewportSize
will be calculated by ResizeObserver so it's 0 until the first measurement.
Cannot find module 'virtua/vue(solid|svelte)' or its corresponding type declarations
error?This package uses exports of package.json for entry point of Vue/Solid/Svelte adapter. This field can't be resolved in TypeScript with moduleResolution: node
. Try moduleResolution: bundler
or moduleResolution: nodenext
instead.
virtua | react-virtuoso | react-window | react-virtualized | @tanstack/react-virtual | react-tiny-virtual-list | react-cool-virtual | |
---|---|---|---|---|---|---|---|
Bundle size | |||||||
Vertical scroll | ✅ | ✅ | ✅ | ✅ | 🟠 (needs customization) | ✅ | 🟠 (needs customization) |
Horizontal scroll | ✅ | ✅ | ✅ (may be dropped in v2) | ✅ | 🟠 (needs customization) | ✅ | 🟠 (needs customization) |
Horizontal scroll in RTL direction | ✅ | ❌ | ✅ (may be dropped in v2) | ❌ | ❌ | ❌ | ❌ |
Grid (Virtualization for two dimension) | 🟠 (experimental_VGrid) | ❌ | ✅ (FixedSizeGrid / VariableSizeGrid) | ✅ (Grid) | 🟠 (needs customization) | ❌ | 🟠 (needs customization) |
Table | 🟠 (needs customization) | ✅ (TableVirtuoso) | 🟠 (needs customization) | 🟠 (Table but it's built with div) | 🟠 (needs customization) | ❌ | 🟠 (needs customization) |
Window scroller | ✅ (WindowVirtualizer) | ✅ | ❌ | ✅ (WindowScroller) | ✅ (useWindowVirtualizer) | ❌ | ❌ |
Dynamic list size | ✅ | ✅ | 🟠 (needs AutoSizer) | 🟠 (needs AutoSizer) | ✅ | ❌ | ✅ |
Dynamic item size | ✅ | ✅ | 🟠 (needs additional codes and has wrong destination when scrolling to item imperatively) | 🟠 (needs CellMeasurer and has wrong destination when scrolling to item imperatively) | 🟠 (has wrong destination when scrolling to item imperatively) | ❌ | 🟠 (has wrong destination when scrolling to item imperatively) |
Reverse scroll | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
Reverse scroll in iOS Safari | 🟠 (user must release scroll) | 🟠 (has glitch with unknown sized items) | ❌ | ❌ | ❌ | ❌ | ❌ |
Infinite scroll | ✅ | ✅ | 🟠 (needs react-window-infinite-loader) | 🟠 (needs InfiniteLoader) | ✅ | ❌ | ✅ |
Reverse (bi-directional) infinite scroll | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | 🟠 (has startItem method but its scroll position can be inaccurate) |
Scroll restoration | ✅ | ✅ (getState) | ❌ | ❌ | ❌ | ❌ | ❌ |
Smooth scroll | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ |
SSR support | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
Render React Server Components (RSC) as children | ✅ | ❌ | ❌ | ❌ | 🟠(needs customization) | ❌ | 🟠 (needs customization) |
Display exceeding browser's max element size limit | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
WIP
All contributions are welcome. If you find a problem, feel free to create an issue or a PR. If you have a question, ask in discussions.
npm install
.No vulnerabilities found.
No security vulnerabilities found.