MCPcopy
hub / github.com/inokawa/virtua

github.com/inokawa/virtua @0.49.2 sqlite

repository ↗ · DeepWiki ↗ · release 0.49.2 ↗
405 symbols 1,040 edges 123 files 49 documented · 12%
README

virtua

npm npm bundle size npm Best of JS Ask DeepWiki check demo

A zero-config, fast and small (~3kB) virtual list (and grid) component for React, Vue, Solid and Svelte.

example

If you want to check the difference with the alternatives right away, see comparison section.

Motivation

This project is a challenge to rethink virtualization. The goals are...

  • 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).
  • Fast: Natural virtual scrolling needs optimization in many aspects (eliminate frame drops by reducing CPU usage and GC, reduce synchronous layout recalculation, reduce visual jumps on repaint, optimize with CSS, optimize for JIT, optimize for frameworks, etc). We are trying to combine the best of them.
  • Small: Its bundle size should be small as much as possible to be friendly with modern web development. Currently each components are ~3kB gzipped and tree-shakeable.
  • Flexible: Aiming to support many usecases - fixed size, dynamic size, horizontal scrolling, reverse scrolling, RTL, mobile, infinite scrolling, scroll restoration, DnD, keyboard navigation, sticky, placeholder and more. See live demo.
  • Framework agnostic: React, Vue, Solid and Svelte are supported. We could support other frameworks in the future.

Demo

https://inokawa.github.io/virtua/

Install

npm install virtua

If you use this lib in legacy browsers which does not have ResizeObserver, you should use polyfill.

Getting started

React

react >= 16.14 is required.

If you use ESM and webpack 5, use react >= 18 to avoid Can't resolve react/jsx-runtime error.

Vertical scroll

import { VList } from "virtua";

// children
export const App = () => {
  return (
    <VList style={{ height: 800 }}>
      {Array.from({ length: 1000 }).map((_, i) => (



          {i}



      ))}
    </VList>
  );
};

// or render prop
export const App = () => {
  const items = Array.from({ length: 1000 }).map(
    () => Math.floor(Math.random() * 10) * 10 + 10,
  );
  return (
    <VList data={items} style={{ height: 800 }}>
      {(d, i) => (



          {i}



      )}
    </VList>
  );
};

Horizontal scroll

import { VList } from "virtua";

export const App = () => {
  return (
    <VList style={{ height: 400 }} horizontal>
      {Array.from({ length: 1000 }).map((_, i) => (



          {i}



      ))}
    </VList>
  );
};

Customization

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.

import { Virtualizer } from "virtua";

export const App = () => {
  return (





header


      <Virtualizer startMargin={40}>
        {Array.from({ length: 1000 }).map((_, i) => (



            {i}



        ))}
      </Virtualizer>



  );
};

Window scroll

import { WindowVirtualizer } from "virtua";

export const App = () => {
  return (



      <WindowVirtualizer>
        {Array.from({ length: 1000 }).map((_, i) => (



            {i}



        ))}
      </WindowVirtualizer>



  );
};

Vertical and horizontal scroll

import { experimental_VGrid as VGrid } from "virtua";

export const App = () => {
  return (
    <VGrid style={{ height: 800 }} row={1000} col={500}>
      {({ rowIndex, colIndex }) => (



          {rowIndex} / {colIndex}



      )}
    </VGrid>
  );
};

Vue

vue >= 3.2 is required.

<script setup>
import { VList } from "virtua/vue";

const sizes = [20, 40, 180, 77];
const data = Array.from({ length: 1000 }).map((_, i) => sizes[i % 4]);
</script>

<template>
  <VList :data="data" :style="{ height: '800px' }" #default="{ item, index }">



      {{ index }}



  </VList>
</template>

Solid

solid-js >= 1.0 is required.

import { VList } from "virtua/solid";

export const App = () => {
  const sizes = [20, 40, 180, 77];
  const data = Array.from({ length: 1000 }).map((_, i) => sizes[i % 4]);

  return (
    <VList data={data} style={{ height: "800px" }}>
      {(d, i) => (



          {i()}



      )}
    </VList>
  );
};

Svelte

svelte >= 5.0 is required.

<script lang="ts">
  import { VList } from "virtua/svelte";

  const sizes = [20, 40, 180, 77];

  const data = Array.from({ length: 1000 }).map((_, i) => sizes[i % 4] );
</script>

<VList {data} style="height: 100vh;" getKey={(_, i) => i}>
  {#snippet children(item, index)}



      {index}



  {/snippet}
</VList>

Other bindings

Documentation

FAQs

Is there any way to improve performance further?

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.

const elements = useMemo(
  () => tooLongArray.map((d) => <Component key={d.id} {...d} />),
  [tooLongArray],
);
const [position, setPosition] = useState(0);
return (





position: {position}


    <VList onScroll={(offset) => setPosition(offset)}>{elements}</VList>



);

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 memoized function or component to reduce calculation and re-rendering during scrolling.

// memoize render function with some memoization library
import memoize from "memoize";

const renderItem = memoize((item: Data) => {
  return <Component key={item.id} data={item} />;
});

<VList data={items}>{renderItem}</VList>;

// memoize component with React.memo
import { memo } from "react";

const Component = memo(HeavyItem);

<VList data={items}>
  {(item) => {
    return <Component key={item.id} data={item} />;
  }}
</VList>;

Decreasing bufferSize 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.

What is 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.

Why my items are squashed(or rendered inconsistently) on resize/add/remove?

Maybe you forgot to pass key prop to each items, or the keys are not unique. Item sizes are stored per key.

And do not use index of items as key, especially when you want to toggle shift prop to true. Prepending will increment every indexes of items and that will cause unexpected behavior.

Why VListHandle.viewportSize is 0 on mount?

viewportSize will be calculated by ResizeObserver so it's 0 until the first measurement.

What is 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.

Comparison

Features

virtua react-virtuoso react-window react-virtualized @tanstack/react-virtual
Bundle size

Extension points exported contracts — how you extend this code

ListItemProps (Interface)
(no doc)
src/react/ListItem.tsx
ListResizer (Interface)
(no doc)
src/core/resizer.ts
ListItemProps (Interface)
(no doc)
src/solid/ListItem.tsx
WindowVirtualizerProps (Interface)
(no doc)
src/svelte/WindowVirtualizer.type.ts
VirtualizerProps (Interface)
(no doc)
src/vue/Virtualizer.tsx
Message (Interface)
(no doc)
stories/react/advanced/Markdown.stories.tsx
VirtualizerHandle (Interface)
(no doc)
src/react/Virtualizer.tsx
WindowListResizer (Interface)
(no doc)
src/core/resizer.ts

Core symbols most depended-on inside this repo

render
called by 99
spec/vue.ts
storyUrl
called by 51
e2e/utils.ts
getScrollable
called by 50
e2e/utils.ts
relativeTop
called by 39
e2e/utils.ts
getItems
called by 35
e2e/utils.ts
getVirtualizer
called by 33
e2e/utils.ts
findIndex
called by 31
src/core/cache.ts
expectInRange
called by 28
e2e/utils.ts

Shape

Function 300
Method 59
Interface 44
Class 2

Languages

TypeScript100%

Modules by API surface

e2e/utils.ts46 symbols
src/core/scroller.ts25 symbols
src/react/VGrid.tsx22 symbols
src/core/resizer.ts20 symbols
src/vue/Virtualizer.tsx16 symbols
src/solid/Virtualizer.tsx14 symbols
src/react/Virtualizer.tsx14 symbols
src/vue/WindowVirtualizer.tsx11 symbols
stories/react/basics/VList.stories.tsx10 symbols
src/vue/VList.tsx10 symbols
src/solid/WindowVirtualizer.tsx10 symbols
src/core/store.ts10 symbols

Dependencies from manifests, versioned

@atlaskit/pragmatic-drag-and-drop-auto-scroll2.1.5 · 1×
@atlaskit/pragmatic-drag-and-drop-hitbox1.1.0 · 1×
@atlaskit/pragmatic-drag-and-drop-react-drop-indicator3.2.12 · 1×
@base-ui/react1.4.0 · 1×
@dnd-kit/core6.3.1 · 1×
@dnd-kit/sortable10.0.0 · 1×
@emotion/react11.13.5 · 1×
@emotion/styled11.13.5 · 1×
@faker-js/faker10.3.0 · 1×
@mui/material9.0.0 · 1×

For agents

$ claude mcp add virtua \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact