svelte-tiny-virtual-list
Version:
A tiny but mighty list virtualization component for svelte, with zero dependencies 💪
240 lines (184 loc) • 14.9 kB
Markdown
<p align="center"><img src="./static/logo.svg" alt="Logo" width="225"></p>
<h2 align="center">svelte-tiny-virtual-list</h2>
<p align="center">A tiny but mighty list virtualization library, with zero dependencies 💪</p>
<p align="center">
<a href="https://npmjs.com/package/svelte-tiny-virtual-list"><img src="https://img.shields.io/npm/v/svelte-tiny-virtual-list?style=for-the-badge" alt="NPM VERSION"></a>
<a href="https://npmjs.com/package/svelte-tiny-virtual-list"><img src="https://img.shields.io/npm/dt/svelte-tiny-virtual-list?style=for-the-badge" alt="NPM DOWNLOADS"></a>
<a href="https://npmjs.com/package/svelte-tiny-virtual-list"><img src="https://img.shields.io/librariesio/release/npm/svelte-tiny-virtual-list?style=for-the-badge" alt="DEPENDENCIES"></a>
</p>
<p align="center">
<a href="#about">About</a> •
<a href="#features">Features</a> •
<a href="#installation">Installation</a> •
<a href="#usage">Usage</a> •
<a href="#examples--demo">Examples</a> •
<a href="#license">License</a>
</p>
## About
Instead of rendering all your data in a huge list, the virtual list component just renders the items that are visible, keeping your page nice and light.
This is heavily inspired by [react-tiny-virtual-list](https://github.com/clauderic/react-tiny-virtual-list) and uses most of its code and functionality!
### Features
- **Tiny & dependency free** – Only ~5kb gzipped
- **Render millions of items**, without breaking a sweat
- **Scroll to index** or **set the initial scroll offset**
- **Supports fixed** or **variable** heights/widths
- **Vertical** or **Horizontal** lists
- [`svelte-infinite-loading`](https://github.com/Skayo/svelte-infinite-loading) compatibility
## Installation
> If you're using this component in a Sapper application, make sure to install the package to `devDependencies`!
> [More Details](https://github.com/sveltejs/sapper-template#using-external-components)
With npm:
```shell
$ npm install svelte-tiny-virtual-list
```
With yarn:
```shell
$ yarn add svelte-tiny-virtual-list
```
With [pnpm](https://pnpm.js.org/) (recommended):
```shell
$ npm i -g pnpm
$ pnpm install svelte-tiny-virtual-list
```
From CDN (via [unpkg](https://unpkg.com/)):
```html
<!-- UMD -->
<script src="https://unpkg.com/svelte-tiny-virtual-list@^1/dist/svelte-tiny-virtual-list.js"></script>
<!-- ES Module -->
<script src="https://unpkg.com/svelte-tiny-virtual-list@^1/dist/svelte-tiny-virtual-list.mjs"></script>
```
## Usage
```svelte
<script>
import VirtualList from 'svelte-tiny-virtual-list';
const data = ['A', 'B', 'C', 'D', 'E', 'F' /* ... */];
</script>
<VirtualList width="100%" height={600} itemCount={data.length} itemSize={50}>
<div slot="item" let:index let:style {style}>
Letter: {data[index]}, Row: #{index}
</div>
</VirtualList>
```
Also works pretty well with [`svelte-infinite-loading`](https://github.com/Skayo/svelte-infinite-loading):
```svelte
<script>
import VirtualList from 'svelte-tiny-virtual-list';
import InfiniteLoading from 'svelte-infinite-loading';
let data = ['A', 'B', 'C', 'D', 'E', 'F' /* ... */];
function infiniteHandler({ detail: { complete, error } }) {
try {
// Normally you'd make an http request here...
const newData = ['G', 'H', 'I', 'J', 'K', 'L' /* ... */];
data = [...data, ...newData];
complete();
} catch (e) {
error();
}
}
</script>
<VirtualList width="100%" height={600} itemCount={data.length} itemSize={50}>
<div slot="item" let:index let:style {style}>
Letter: {data[index]}, Row: #{index}
</div>
<div slot="footer">
<InfiniteLoading on:infinite={infiniteHandler} />
</div>
</VirtualList>
```
### Props
| Property | Type | Required? | Description |
| ----------------- | ------------------------------------------------- | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| width | `number \| string`\* | ✓ | Width of List. This property will determine the number of rendered items when scrollDirection is `'horizontal'`. |
| height | `number \| string`\* | ✓ | Height of List. This property will determine the number of rendered items when scrollDirection is `'vertical'`. |
| itemCount | `number` | ✓ | The number of items you want to render |
| itemSize | `number \| number[] \| (index: number) => number` | ✓ | Either a fixed height/width (depending on the scrollDirection), an array containing the heights of all the items in your list, or a function that returns the height of an item given its index: `(index: number): number` |
| scrollDirection | `string` | | Whether the list should scroll vertically or horizontally. One of `'vertical'` (default) or `'horizontal'`. |
| scrollOffset | `number` | | Can be used to control the scroll offset; Also useful for setting an initial scroll offset |
| scrollToIndex | `number` | | Item index to scroll to (by forcefully scrolling if necessary) |
| scrollToAlignment | `string` | | Used in combination with `scrollToIndex`, this prop controls the alignment of the scrolled to item. One of: `'start'`, `'center'`, `'end'` or `'auto'`. Use `'start'` to always align items to the top of the container and `'end'` to align them bottom. Use `'center`' to align them in the middle of the container. `'auto'` scrolls the least amount possible to ensure that the specified `scrollToIndex` item is fully visible. |
| scrollToBehaviour | `string` | | Used in combination with `scrollToIndex`, this prop controls the behaviour of the scrolling. One of: `'auto'`, `'smooth'` or `'instant'` (default). |
| stickyIndices | `number[]` | | An array of indexes (eg. `[0, 10, 25, 30]`) to make certain items in the list sticky (`position: sticky`) |
| overscanCount | `number` | | Number of extra buffer items to render above/below the visible items. Tweaking this can help reduce scroll flickering on certain browsers/devices. |
| estimatedItemSize | `number` | | Used to estimate the total size of the list before all of its items have actually been measured. The estimated total height is progressively adjusted as items are rendered. |
| getKey | `(index: number) => any` | | Function that returns the key of an item in the list, which is used to uniquely identify an item. This is useful for dynamic data coming from a database or similar. By default, it's using the item's index. |
_\* `height` must be a number when `scrollDirection` is `'vertical'`. Similarly, `width` must be a number if `scrollDirection` is `'horizontal'`_
### Slots
- `item` - Slot for each item
- Props:
- `index: number` - Item index
- `style: string` - Item style, must be applied to the slot (look above for example)
- `header` - Slot for the elements that should appear at the top of the list
- `footer` - Slot for the elements that should appear at the bottom of the list (e.g. `InfiniteLoading` component from `svelte-infinite-loading`)
### Events
- `afterScroll` - Fired after handling the scroll event
- `detail` Props:
- `event: ScrollEvent` - The original scroll event
- `offset: number` - Either the value of `wrapper.scrollTop` or `wrapper.scrollLeft`
- `itemsUpdated` - Fired when the visible items are updated
- `detail` Props:
- `start: number` - Index of the first visible item
- `end: number` - Index of the last visible item
### Methods
- `recomputeSizes(startIndex: number)` - This method force recomputes the item sizes after the specified index (these are normally cached).
`VirtualList` has no way of knowing when its underlying data has changed, since it only receives a itemSize property. If the itemSize is a `number`, this isn't an issue, as it can compare before and after values and automatically call `recomputeSizes` internally.
However, if you're passing a function to `itemSize`, that type of comparison is error prone. In that event, you'll need to call `recomputeSizes` manually to inform the `VirtualList` that the size of its items has changed.
#### Use the methods like this:
```svelte
<script>
import { onMount } from 'svelte';
import VirtualList from 'svelte-tiny-virtual-list';
const data = ['A', 'B', 'C', 'D', 'E', 'F' /* ... */];
let virtualList;
function handleClick() {
virtualList.recomputeSizes(0);
}
</script>
<button on:click={handleClick}>Recompute Sizes</button>
<VirtualList
bind:this={virtualList}
width="100%"
height={600}
itemCount={data.length}
itemSize={50}
>
<div slot="item" let:index let:style {style}>
Letter: {data[index]}, Row: #{index}
</div>
</VirtualList>
```
### Styling
You can style the elements of the virtual list like this:
```svelte
<script>
import VirtualList from 'svelte-tiny-virtual-list';
const data = ['A', 'B', 'C', 'D', 'E', 'F' /* ... */];
</script>
<div class="list">
<VirtualList width="100%" height={600} itemCount={data.length} itemSize={50}>
<div slot="item" let:index let:style {style}>
Letter: {data[index]}, Row: #{index}
</div>
</VirtualList>
</div>
<style>
.list :global(.virtual-list-wrapper) {
background-color: #0f0;
/* ... */
}
.list :global(.virtual-list-inner) {
background-color: #f00;
/* ... */
}
</style>
```
## Examples / Demo
- **Basic setup**
- [Elements of equal height](https://svelte.dev/playground/e3811b44f311461dbbc7c2df830cde68)
- [Variable heights](https://svelte.dev/playground/93795c812f8d4541b6b942535b2ed855)
- [Horizontal list](https://svelte.dev/playground/4cd8acdfc96843b68265a19451b1bf3d)
- **Controlled props**
- [Scroll to index](https://svelte.dev/playground/bdf5ceb63f6e45f7bb14b90dbd2c11d9)
- [Controlled scroll offset](https://svelte.dev/playground/68576a3919c44033a74416d4bc4fde7e)
- [Hacker News using svelte-infinite-loading](https://svelte.dev/playground/2239cc4c861c41d18abbc858248f5a0d)
## License
[MIT License](https://github.com/Skayo/svelte-tiny-virtual-list/blob/master/LICENSE)