Virtual Scroll Grid for Vue 3
vue-virtual-scroll-grid
This is a reusable component for Vue 3 that renders a list with a huge number of items (e.g. 1000+ items) as a grid in a performant way.
Features
- Use virtual-scrolling / windowing to render the items, so the number of DOM
nodes is kept low. - Support using a paginated API to load the items in the background.
- Support placeholders for unloaded items and loaded items are cached for better
performance. - Just use CSS grid to style your grid. Minimum styling opinions form the
library.
Install
npm install vue-virtual-scroll-grid
Example
<template>
<!--
length: The number of items in the list.
pageSize: The number of items in each page returned by the page provider.
pageProvider: The callback that returns a page of items as a promise.
-->
<Grid :length="1000" :pageSize="10" :pageProvider="pageProvider" class="grid">
<template v-slot:probe>
<div class="item">Probe</div>
</template>
<!-- When the item is not loaded, a placeholder is rendered -->
<template v-slot:placeholder="{ index, style }">
<div class="item" :style="style">Placeholder {{ index }}</div>
</template>
<!-- Render a loaded item -->
<template v-slot:default="{ item, style, index }">
<div class="item" :style="style">{{ item }} {{ index }}</div>
</template>
</Grid>
</template>
<script>
import Grid from "vue-virtual-scroll-grid";
export default {
name: "App",
components: { Grid },
setup: () => ({
// Return items for the given page after a 0-3 second randomly
pageProvider: (pageNumber, pageSize) =>
new Promise((resolve) =>
setTimeout(
() => resolve(new Array(pageSize).fill("Loaded Item")),
Math.round(3000 * Math.random())
)
),
}),
};
</script>
<style>
.grid {
display: grid;
grid-gap: 20px;
grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 768px) { .grid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 992px) { .grid { grid-template-columns: repeat(4, 1fr); } }
@media (min-width: 1280px) { .grid { grid-template-columns: repeat(4, 1fr); } }
@media (min-width: 1440px) { .grid { grid-template-columns: repeat(5, 1fr); } }
@media (min-width: 1650px) { .grid { grid-template-columns: repeat(6, 1fr); } }
@media (min-width: 1890px) { .grid { grid-template-columns: repeat(7, 1fr); } }
@media (min-width: 2530px) { .grid { grid-template-columns: repeat(8, 1fr); } }
.item {
background-color: lightgray;
padding: 100px 0;
text-align: center;
}
</style>
Available Props
interface Props {
// The number of items in the list.
// Required and must be an integer and greater than or equal to 0.
length: number;
// The callback that returns a page of items as a promise.
// Required.
pageProvider: (pageNumber: number, pageSize: number) => Promise<unknown[]>;
// The number of items in a page from the item provider (e.g. a backend API).
// Required and must be an integer and greater than 1.
pageSize: number;
}
Example:
<Grid :length="1000"
:pageSize="40"
:pageProvider="async (pageNumber, pageSize) => Array(pageSize).fill('x')"
/>
Available Slots
There are 3 scoped slots: default
, placeholder
and probe
.
The default
slot
The default
slot is used to render a loaded item.
Props:
item
: the loaded item that is used for rendering your item
element/component.index
: the index of current item within the list.style
: the style object provided by the library that need to be set on the
item element/component.
Example:
<template v-slot:default="{ item, style, index }">
<div :style="style">{{ item }} {{ index }}</div>
</template>
Theplaceholder
slot
When an item is not loaded, the component/element in the placeholder
slot will
be used for rendering. The placeholder
slot is optional. If missing, the space
of unloaded items will be blank until they are loaded.
Props:
index
: the index of current item within the list.style
: the style object provided by the library that need to be set on the
item element/component.
Example:
<template v-slot:placeholder="{ index, style }">
<div :style="style">Placeholder {{ index }}</div>
</template>
The probe
slot
The probe
slot is used to measure the visual size of grid item. It has no
prop. You can pass the same element/component for the
placeholder
slot. If not provided, you must set a fixed height
to grid-template-rows
on your CSS grid, e.g. 200px
. Otherwise, the view
won't be rendered properly.
Example:
<template v-slot:probe>
<div class="item">Probe</div>
</template>
Caveats
The library does not require items have foreknown width and height, but do
require them to be styled with the same width and height under a view. E.g. the
items can be 200px x 200px when the view is under 768px and 300px x 500px above
768px.
Development
- Setup:
npm install
- Run dev server:
npm run dev
- Lint (type check):
npm run lint
- Build the library:
npm run build
- Build the demo:
npm run build -- --mode=demo
- Preview the locally built demo:
npm run serve