Skip to content

Repository files navigation

Lazy Virtual Scroll

A monorepo containing high-performance virtualized list components for React and Vue that efficiently render large datasets with dynamic sizing, lazy loading, and bi-directional scrolling support.

License: MIT

Packages

This monorepo contains the following packages:

Package Description NPM
@lazy-virtual-scroll/react Virtualized list component for React npm version
@lazy-virtual-scroll/vue Virtualized list component for Vue 3 npm version
core Core utilities used by both React and Vue implementations -

Features

  • Virtualized Rendering: Only renders the items currently visible in the viewport
  • Dynamic Sizing: Automatically detects and handles items of varying heights
  • Lazy Loading: Load data on-demand as the user scrolls
  • Bi-directional Scrolling: Support for both vertical and horizontal scrolling
  • Performance Optimized: Debounced and throttled scroll handling
  • Flexible Data Structure: Support for continuous or fragmented datasets
  • Background Loading: Optionally cache the whole list off the critical path and query it asynchronously, with IndexedDB storage so it never has to sit in memory
  • TypeScript Support: Full type definitions included

Quick Start

React

npm install @lazy-virtual-scroll/react
import LazyVirtualScroll from '@lazy-virtual-scroll/react';

<LazyVirtualScroll
  totalItems={10000}
  itemSize={50}
  data={items}
  onLoad={({ startIndex, endIndex }) => {
    console.log(`Loading items ${startIndex} to ${endIndex}`);
  }}
  onHide={({ startIndex, endIndex }) => {
    console.log(`Hiding items ${startIndex} to ${endIndex}`);
  }}
  render={(index, item) => (
    <div style={{
      height: '50px',
      padding: '10px',
      borderBottom: '1px solid #eee',
      boxSizing: 'border-box'
    }}>
      {item ? item.text : 'Loading...'}
    </div>
  )}
  renderLoading={(index) => (
    <div style={{
      height: '50px', 
      padding: '10px',
      backgroundColor: '#f5f5f5',
      boxSizing: 'border-box'
    }}>
      Loading item {index}...
    </div>
  )}
/>

Vue

npm install @lazy-virtual-scroll/vue
<LazyVirtualScroll
  :totalItems="10000"
  :itemSize="50"
  :data="items"
  @load="({ startIndex, endIndex }) => {
    console.log(`Loading items ${startIndex} to ${endIndex}`);
  }"
  @hide="({ startIndex, endIndex }) => {
    console.log(`Hiding items ${startIndex} to ${endIndex}`);
  }"
>
  <template #default="{ item, index }">
    <div style="
      height: 50px;
      padding: 10px;
      border-bottom: 1px solid #eee;
      box-sizing: border-box;
    ">
      {{ item.text }}
    </div>
  </template>
  <template #loading="{ index }">
    <div style="
      height: 50px;
      padding: 10px;
      background-color: #f5f5f5;
      box-sizing: border-box;
    ">
      Loading item {{ index }}...
    </div>
  </template>
</LazyVirtualScroll>

Background Loading & Data Access

By default the components are pure views — you own the rows and pass them in, and onLoad/@load tells you when to fetch more. That covers rendering, but not reaching a row nobody has scrolled to, which is what search, export and "select all" need.

Passing a data source inverts that. You supply one fetchRange function and the source owns a row cache that fills the viewport as you scroll, optionally walks the entire list in the background (off the critical path, rendering nothing), and exposes the whole thing through an async API:

// React
const source = useLazyDataSource<User>({
  totalItems: 1000000,
  fetchRange: (startIndex, endIndex) => loadUsers(startIndex, endIndex),
  useIndexedDb: true,                 // spill rows off the heap
  background: { autoStart: true },    // index the whole list in the background
});

<LazyVirtualScroll totalItems={1000000} itemSize={50} source={source} render={...} />

// Anywhere else — search without ever holding the list in memory
for await (const { startIndex, rows } of source.scan()) {
  /* ...filter each batch... */
}
<!-- Vue -->
<LazyVirtualScroll :totalItems="1000000" :itemSize="50" :source="source" />

Every method is async whichever backend is in use, so turning useIndexedDb on or off never changes calling code. The feature is entirely opt-in: without a source prop nothing about the components changes.

Full documentation: React · Vue

Documentation

For complete documentation and examples, please refer to the README files for each package:

Examples

This repository includes demo applications for both React and Vue:

  • React Demo: apps/demo-react
  • Vue Demo: apps/demo-vue

To run the demos:

# React demo
npx nx serve demo-react

# Vue demo
npx nx serve demo-vue

Development

This workspace is powered by Nx.

Building the packages

# Build all packages
npx nx run-many -t build

# Build specific package
npx nx build @lazy-virtual-scroll/react
npx nx build @lazy-virtual-scroll/vue
npx nx build core

Running tests

# Test all packages
npx nx run-many -t test

# Test specific package
npx nx test @lazy-virtual-scroll/react
npx nx test @lazy-virtual-scroll/vue
npx nx test core

License

MIT

Learn more about code generators and inferred tasks in the docs.

Running tasks

To execute tasks with Nx use the following syntax:

npx nx <target> <project> <...options>

You can also run multiple targets:

npx nx run-many -t <target1> <target2>

..or add -p to filter specific projects

npx nx run-many -t <target1> <target2> -p <proj1> <proj2>

Targets can be defined in the package.json or projects.json. Learn more in the docs.

Set up CI!

Nx comes with local caching already built-in (check your nx.json). On CI you might want to go a step further.

Explore the project graph

Run npx nx graph to show the graph of the workspace. It will show tasks that you can run with Nx.

Connect with us!

About

HTML Scroller that supports dynamic lazy loading as well as virtual scroll size based on a total result count but not render any additional dom elements to manage the scroll height.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages