UNPKG

svelte-tiny-virtual-list

Version:

A tiny but mighty list virtualization component for svelte, with zero dependencies 💪

240 lines (184 loc) • 14.9 kB
<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 &#128170;</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)