GithubHelp home page GithubHelp logo

inokawa / virtua Goto Github PK

View Code? Open in Web Editor NEW
943.0 6.0 31.0 80.96 MB

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

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

License: MIT License

JavaScript 1.42% TypeScript 96.94% HTML 0.03% CSS 0.38% Vue 1.24%
infinite-scroll performance react react-component virtualization virtualized windowing virtual-scroll scrolling react-server-components

virtua's Introduction

virtua

npm npm bundle size npm Best of JS check demo

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

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 and layout recalculation, reduce visual jumps on repaint, optimize with CSS, 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. The total size for React is ~5kB gzipped.
  • 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 and Solid 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";

export const App = () => {
  return (
    <VList style={{ height: 800 }}>
      {Array.from({ length: 1000 }).map((_, i) => (
        <div
          key={i}
          style={{
            height: Math.floor(Math.random() * 10) * 10 + 10,
            borderBottom: "solid 1px gray",
            background: "white",
          }}
        >
          {i}
        </div>
      ))}
    </VList>
  );
};

Horizontal scroll

import { VList } from "virtua";

export const App = () => {
  return (
    <VList style={{ height: 400 }} horizontal>
      {Array.from({ length: 1000 }).map((_, i) => (
        <div
          key={i}
          style={{
            width: Math.floor(Math.random() * 10) * 10 + 10,
            borderRight: "solid 1px gray",
            background: "white",
          }}
        >
          {i}
        </div>
      ))}
    </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 (
    <div style={{ overflowY: "auto", height: 800 }}>
      <div style={{ height: 40 }}>header</div>
      <Virtualizer startMargin={40}>
        {Array.from({ length: 1000 }).map((_, i) => (
          <div
            key={i}
            style={{
              height: Math.floor(Math.random() * 10) * 10 + 10,
              borderBottom: "solid 1px gray",
              background: "white",
            }}
          >
            {i}
          </div>
        ))}
      </Virtualizer>
    </div>
  );
};

Window scroll

import { WindowVirtualizer } from "virtua";

export const App = () => {
  return (
    <div style={{ padding: 200 }}>
      <WindowVirtualizer>
        {Array.from({ length: 1000 }).map((_, i) => (
          <div
            key={i}
            style={{
              height: Math.floor(Math.random() * 10) * 10 + 10,
              borderBottom: "solid 1px gray",
              background: "white",
            }}
          >
            {i}
          </div>
        ))}
      </WindowVirtualizer>
    </div>
  );
};

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 }) => (
        <div
          style={{
            width: ((colIndex % 3) + 1) * 100,
            border: "solid 1px gray",
            background: "white",
          }}
        >
          {rowIndex} / {colIndex}
        </div>
      )}
    </VGrid>
  );
};

React Server Components (RSC) support

This library is marked as a Client Component. You can render RSC as children of VList, Virtualizer or WindowVirtualizer.

// page.tsx in App Router of Next.js

export default async () => {
  const articles = await fetchArticles();
  return (
    <div>
      <div>This is Server Component</div>
      <VList style={{ height: 300 }}>
        {articles.map((a) => (
          <div key={a.id} style={{ border: "solid 1px gray", height: 80 }}>
            {a.content}
          </div>
        ))}
      </VList>
    </div>
  );
};

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) => ({
  id: i,
  size: sizes[i % 4] + "px",
}));
</script>

<template>
  <VList :data="data" :style="{ height: '800px' }" #default="item">
    <div
      :key="item.id"
      :style="{
        height: item.size,
        background: 'white',
        borderBottom: 'solid 1px #ccc',
      }"
    >
      {{ item.id }}
    </div>
  </VList>
</template>

Solid

solid-js >= 1.0 is required.

import { VList } from "virtua/solid";

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

  return (
    <VList data={data} style={{ height: "800px" }}>
      {(d, i) => (
        <div
          style={{
            height: d + "px",
            "border-bottom": "solid 1px #ccc",
            background: "#fff",
          }}
        >
          {i}
        </div>
      )}
    </VList>
  );
};

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 (
  <div>
    <div>position: {position}</div>
    <VList onScroll={(offset) => setPosition(offset)}>{elements}</VList>
  </div>
);

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.

const Component = memo(HeavyItem);

<VList count={items.length}>
  {(i) => {
    const item = items[i];
    return <Component key={item.id} data={item} />;
  }}
</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.

What is ResizeObserver loop completed with undelivered notifications. error?

It may be dispatched by ResizeObserver in this lib as described in spec. 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 VListHandle.viewportSize is 0 on mount?

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

Comparison

Features

virtua react-virtuoso react-window react-virtualized @tanstack/react-virtual react-tiny-virtual-list react-cool-virtual
Bundle size npm bundle size npm bundle size npm bundle size npm bundle size npm bundle size npm bundle size npm 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 โœ… ๐ŸŸ  (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 โŒ โŒ โŒ โœ… โŒ โŒ โŒ
  • โœ… - Built-in supported
  • ๐ŸŸ  - Supported but partial, limited or requires some user custom code
  • โŒ - Not officially supported

Benchmarks

WIP

Contribute

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.

Making a Pull Request

  1. Fork this repo.
  2. Run npm install.
  3. Commit your fix.
  4. Make a PR and confirm all the CI checks passed.

virtua's People

Contributors

aeharding avatar bor3ham avatar caprolactam avatar dependabot[bot] avatar ekm1 avatar inokawa avatar mstrvlt avatar renovate[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

virtua's Issues

Loading more items in mode: reverse

Is your feature request related to a problem? Please describe.
I use virtua in a chat message list and when scroll hits the top we load more messages. But we lose scroll state and either jump to offset: 0 at top or far bottom.

Describe the solution you'd like
It should have a way to pin scroll to top most item in the viewport so user doesn't feel any jumps.

Describe alternatives you've considered
Using a useLayoutEffect to scroll to that item manually works but is janky and not smooth.

Additional context
In general, mode: reverse scroll positioning can be improved, eg. to load at the bottom initially and not top, keep at bottom when item sizes change, etc. We can implement most of these given flexible APIs. What's important is user shouldn't feel any frames in a between state that preserves like a jump.

Pluggable architecture

Is your feature request related to a problem? Please describe.
Virtual scrolling has many usecases so its bundle size will continue to grow as we add more features.
However most of the features would be for people with specific needs. For example someone needs rtl support but others not, someone needs smooth scrolling but others not. Someone renders millions of items but others only render thousands of items.

Describe the solution you'd like
under investigation...

Describe alternatives you've considered
N/A

Additional context
N/A

Rearchitect for optimization at browser-level

Is your feature request related to a problem? Please describe.
We achieved optimization in this lib at sufficient level, with improving js, css and React technique.
However it seems to be difficult to be the fastest without rethinking its architecture to become suitable for optimization at browser level.
Of course it would make it difficult to maintain, so we have to choose the one which has clear benefits and acceptable cons.

Describe the solution you'd like

  • DOM recycling
  • Synchronous scrolling
    • implemented in Monaco Editor and (probably) older CodeMirror
    • cons: probably hard to implement smoother than native asynchronous scrolling
  • or something better (need to investigate)

Describe alternatives you've considered
N/A

Additional context
N/A

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Awaiting Schedule

These updates are awaiting their schedule. Click on a checkbox to get an update now.

  • Pin dependencies (@storybook/addon-storysource, @storybook/react, @storybook/react-vite, @storybook/source-loader, @storybook/test-runner, @storybook/vue3, @storybook/vue3-vite, storybook, storybook-solidjs, storybook-solidjs-vite)
  • Update dependency @mui/material to v5.15.14
  • Update dependency @testing-library/react to v14.2.2
  • Update dependency @testing-library/vue to v8.0.3
  • Update dependency @types/react to v18.2.72
  • Update dependency @vue/babel-plugin-jsx to v1.2.2
  • Update dependency babel-preset-solid to v1.8.16
  • Update dependency eslint-plugin-solid to v0.13.2
  • Update dependency solid-js to v1.8.16
  • Update dependency typedoc to v0.25.12
  • Update dependency vue to v3.4.21
  • Update dependency @babel/plugin-transform-react-pure-annotations to v7.24.1
  • Update dependency @playwright/test to v1.42.1
  • Update dependency @size-limit/preset-small-lib to v11.1.2
  • Update dependency @tanstack/react-virtual to v3.2.0
  • Update dependency @typescript-eslint/parser to v6.21.0
  • Update dependency eslint to v8.57.0
  • Update dependency eslint-plugin-vue to v9.24.0
  • Update dependency react-virtuoso to v4.7.4
  • Update dependency rollup to v4.13.0
  • Update dependency size-limit to v11.1.2
  • Update dependency typescript to v5.4.3
  • Update dependency virtua to v0.29.1
  • Update dependency vite to v5.2.6
  • Update dependency vite-plugin-solid to v2.10.2
  • Update dependency vitest to v1.4.0
  • Update emotion monorepo (@emotion/react, @emotion/styled)
  • Update actions/setup-node action to v4
  • Update dependency @typescript-eslint/parser to v7
  • Update dependency cmdk to v1
  • Update dependency react-content-loader to v7

Detected dependencies

github-actions
.github/workflows/check.yml
  • actions/checkout v4
  • actions/setup-node v3
  • actions/checkout v4
  • actions/setup-node v3
  • actions/checkout v4
  • actions/setup-node v3
  • actions/checkout v4
  • actions/setup-node v3
.github/workflows/demo.yml
  • actions/checkout v4
  • actions/setup-node v3
  • peaceiris/actions-gh-pages v3
npm
package.json
  • @atlaskit/pragmatic-drag-and-drop 1.1.3
  • @atlaskit/pragmatic-drag-and-drop-auto-scroll 1.0.3
  • @atlaskit/pragmatic-drag-and-drop-hitbox 1.0.3
  • @atlaskit/pragmatic-drag-and-drop-react-drop-indicator 1.0.4
  • @babel/plugin-transform-react-pure-annotations 7.23.3
  • @dnd-kit/core 6.1.0
  • @dnd-kit/sortable 8.0.0
  • @emotion/react 11.10.8
  • @emotion/styled 11.10.8
  • @faker-js/faker 8.4.1
  • @formkit/auto-animate 0.8.1
  • @mui/material 5.15.7
  • @playwright/test 1.41.1
  • @radix-ui/colors 3.0.0
  • @radix-ui/react-scroll-area 1.0.5
  • @rollup/plugin-babel 6.0.4
  • @rollup/plugin-terser 0.4.4
  • @rollup/plugin-typescript 11.1.6
  • @size-limit/preset-small-lib 11.0.2
  • @storybook/addon-storysource ^8.0.0
  • @storybook/react ^8.0.0
  • @storybook/react-vite ^8.0.0
  • @storybook/source-loader ^8.0.0
  • @storybook/test-runner ^0.17.0
  • @storybook/vue3 ^8.0.0
  • @storybook/vue3-vite ^8.0.0
  • @tanstack/react-virtual 3.0.4
  • @testing-library/react 14.2.0
  • @testing-library/vue 8.0.2
  • @types/jsdom 21.1.6
  • @types/react 18.2.55
  • @types/react-beautiful-dnd 13.1.8
  • @types/react-virtualized 9.21.29
  • @types/react-window 1.8.8
  • @typescript-eslint/parser 6.20.0
  • @vitejs/plugin-vue 5.0.4
  • @vitejs/plugin-vue-jsx 3.1.0
  • @vue/babel-plugin-jsx 1.2.1
  • babel-preset-solid 1.8.12
  • cmdk 0.2.1
  • concurrently 8.2.2
  • eslint 8.56.0
  • eslint-import-resolver-typescript 3.6.1
  • eslint-plugin-import 2.29.1
  • eslint-plugin-react-hooks 4.6.0
  • eslint-plugin-solid 0.13.1
  • eslint-plugin-vue 9.21.1
  • jsdom 24.0.0
  • re-resizable 6.9.11
  • react 18.2.0
  • react-beautiful-dnd 13.1.1
  • react-content-loader 6.2.1
  • react-dom 18.2.0
  • react-is 18.2.0
  • react-merge-refs 2.1.1
  • react-virtualized 9.22.5
  • react-virtuoso 4.6.3
  • react-window 1.8.10
  • rimraf 5.0.5
  • rollup 4.10.0
  • rollup-plugin-banner2 1.2.2
  • size-limit 11.0.2
  • solid-js 1.8.14
  • storybook ^8.0.0
  • storybook-solidjs ^1.0.0-beta.2
  • storybook-solidjs-vite ^1.0.0-beta.2
  • typedoc 0.25.8
  • typedoc-plugin-markdown 3.17.1
  • typedoc-plugin-vue 1.1.0
  • typescript 5.3.3
  • virtua 0.27.0
  • vite 5.1.1
  • vite-plugin-solid 2.9.1
  • vitest 1.2.2
  • vue 3.4.19
  • vue-eslint-parser 9.4.2
  • wait-on 7.2.0
  • react >=16.14.0
  • react-dom >=16.14.0
  • solid-js >=1.0
  • vue >=3.2

  • Check this box to trigger a request for Renovate to run again on this repository

How to add private scrollbar in list?

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

Describe the solution you'd like
A clear and concise description of what you want to happen.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context
Add any other context or screenshots about the feature request here.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.