Skip to content
This repository was archived by the owner on Nov 5, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ items (e.g. 1000+ items) as a grid in a performant way.
- Just use CSS grid to style your grid. Minimum styling opinions form the
library.
- Support using a paginated API to load the items in the background.
- Support rendering placeholders for unloaded items
- Support rendering placeholders for unloaded items.
- Support both vertical and horizontal scroll.
- Loaded items are cached for better performance.

## Code Examples
Expand All @@ -36,7 +37,7 @@ npm install vue-virtual-scroll-grid
| `pageProviderDebounceTime` | Debounce window in milliseconds on the calls to `pageProvider` | `number` | Optional, an integer greater than or equal to 0 |
| `pageSize` | The number of items in a page from the item provider (e.g. a backend API) | `number` | Required, an integer greater than or equal to 1 |
| `scrollTo` | Scroll to a specific item by index | `number` | Optional, an integer from 0 to the `length` prop - 1 |
| `scrollBehavior` | The behavior of `scrollTo`. Default value is `smooth` | `smooth` | `auto` | Optional, a string to be `smooth` or `auto` |
| `scrollBehavior` | The behavior of `scrollTo`. Default value is `smooth` | `smooth` | `auto` | Optional, a string to be `smooth` or `auto` |

Example:

Expand Down Expand Up @@ -113,6 +114,11 @@ Example:
</template>
```

## Scroll Mode

The library uses `grid-auto-flow` CSS property to infer scroll mode. Set it to
`column` value if you want to enable horizontal scroll.

## Caveats

The library does not require items have foreknown width and height, but do
Expand Down
37 changes: 24 additions & 13 deletions src/Grid.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
<template>
<div
v-show="length > 0"
ref="rootRef"
:style="{
height: `${contentHeight}px`,
placeContent: 'start',
}"
>
<div v-show="length > 0" ref="rootRef" :style="rootStyles">
<div
:style="{
opacity: 0,
Expand Down Expand Up @@ -40,7 +33,14 @@
</template>

<script lang="ts">
import { defineComponent, onUpdated, PropType, ref } from "vue";
import {
defineComponent,
onUpdated,
PropType,
ref,
computed,
StyleValue,
} from "vue";
import {
fromProp,
fromResizeObserver,
Expand Down Expand Up @@ -100,7 +100,7 @@ export default defineComponent({
// data to render
const {
buffer$, // the items in the current scanning window
contentHeight$, // the height of the whole list
contentSize$, // the size of the whole list
scrollAction$, // the value sent to window.scrollTo()
} = pipeline({
// streams of prop
Expand All @@ -119,17 +119,28 @@ export default defineComponent({

onUpdated(
once(() => {
scrollAction$.subscribe(([el, top]: ScrollAction) => {
el.scrollTo({ top, behavior: props.scrollBehavior });
scrollAction$.subscribe(({ target, offset }: ScrollAction) => {
target.scrollTo({ ...offset, behavior: props.scrollBehavior });
});
})
);

const contentSize = useObservable(contentSize$);
const rootStyles = computed<StyleValue>(() =>
Object.fromEntries([
...Object.entries(contentSize.value ?? {}).map(([property, value]) => [
property,
value + "px",
]),
["placeContent", "start"],
])
);

return {
rootRef,
probeRef,
buffer: useObservable(buffer$),
contentHeight: useObservable(contentHeight$),
rootStyles,
};
},
});
Expand Down
151 changes: 104 additions & 47 deletions src/demo/App.vue
Original file line number Diff line number Diff line change
@@ -1,42 +1,46 @@
<template>
<Header />

<Grid
:length="length"
:pageSize="pageSize"
:pageProvider="pageProvider"
:pageProviderDebounceTime="0"
:scrollTo="scrollTo"
:scrollBehavior="scrollBehavior"
:class="$style.grid"
>
<template v-slot:probe>
<ProductItem sizes="(min-width: 768px) 360px, 290px" />
</template>

<template v-slot:placeholder="{ style }">
<ProductItem :style="style" sizes="(min-width: 768px) 360px, 290px" />
</template>

<template v-slot:default="{ item, style }">
<ProductItem
:handle="item.handle"
:price="item.price * 100"
:compare-at-price="item.compare_at_price * 100"
:published-at="new Date(item.published_at)"
:style="style"
:master-src="item.product_image"
:initial-alt-master-src="true"
:alt="item.title"
sizes="(min-width: 768px) 360px, 290px"
:tags="item.tags"
:vendor="item.vendor"
:title="item.title"
/>
</template>
</Grid>

<Control />
<div :class="$style.root">
<Header :class="$style.header" />

<div :class="$style.gridWrapper">
<Grid
:length="length"
:pageSize="pageSize"
:pageProvider="pageProvider"
:pageProviderDebounceTime="0"
:scrollTo="scrollTo"
:scrollBehavior="scrollBehavior"
:class="[$style.grid, $style[scrollMode]]"
>
<template v-slot:probe>
<ProductItem sizes="(min-width: 768px) 360px, 290px" />
</template>

<template v-slot:placeholder="{ style }">
<ProductItem :style="style" sizes="(min-width: 768px) 360px, 290px" />
</template>

<template v-slot:default="{ item, style }">
<ProductItem
:handle="item.handle"
:price="item.price * 100"
:compare-at-price="item.compare_at_price * 100"
:published-at="new Date(item.published_at)"
:style="style"
:master-src="item.product_image"
:initial-alt-master-src="true"
:alt="item.title"
sizes="(min-width: 768px) 360px, 290px"
:tags="item.tags"
:vendor="item.vendor"
:title="item.title"
/>
</template>
</Grid>
</div>

<Control :class="$style.controls" />
</div>
</template>

<script lang="ts">
Expand All @@ -45,7 +49,14 @@ import Grid from "../Grid.vue";
import Header from "./Header.vue";
import Control from "./Control.vue";
import ProductItem from "./ProductItem.vue";
import { length, pageSize, pageProvider, scrollTo, scrollBehavior } from "./store";
import {
length,
pageSize,
pageProvider,
scrollMode,
scrollTo,
scrollBehavior
} from "./store";

export default defineComponent({
name: "App",
Expand All @@ -54,6 +65,7 @@ export default defineComponent({
length,
pageSize,
pageProvider,
scrollMode,
scrollTo,
scrollBehavior,
}),
Expand Down Expand Up @@ -83,61 +95,106 @@ body {
background-color: var(--color-white);
}

.root {
display: grid;
grid-template: "header" "gridWrapper" 1fr "controls";
height: 100vh;
}

.header {
grid-area: header;
}

.gridWrapper {
grid-area: gridWrapper;
height: 100%;
overflow: auto;
}

.controls {
grid-area: controls;
}

.grid {
display: grid;
padding: 0 1rem;
grid-gap: 2rem;
grid-template-columns: repeat(2, 1fr);
place-items: start stretch;
box-sizing: content-box;
}

.grid.vertical {
grid-template-columns: repeat(2, 1fr);
}

.grid.horizontal {
grid-auto-flow: column;
grid-template-columns: 200px;
grid-template-rows: repeat(2, 1fr);
}

@media (min-width: 760px) {
.grid {
padding: 1.5rem;
grid-gap: 3rem;
}

.grid.vertical {
grid-template-columns: repeat(3, 1fr);
}
}

@media (min-width: 1140px) {
.grid {
.grid.vertical {
grid-template-columns: repeat(4, 1fr);
}
}

@media (min-width: 1520px) {
.grid {
.grid.vertical {
grid-template-columns: repeat(5, 1fr);
}
}

@media (min-width: 1900px) {
.grid {
.grid.vertical {
grid-template-columns: repeat(6, 1fr);
}
}

@media (min-width: 2280px) {
.grid {
.grid.vertical {
grid-template-columns: repeat(7, 1fr);
}
}

@media (min-width: 2660px) {
.grid {
.grid.vertical {
grid-template-columns: repeat(8, 1fr);
}
}

@media (min-width: 3040px) {
.grid {
.grid.vertical {
grid-template-columns: repeat(9, 1fr);
}
}

@media (min-width: 3420px) {
.grid {
.grid.vertical {
grid-template-columns: repeat(10, 1fr);
}
}

@media (min-height: 721px) {
.grid.horizontal {
grid-template-rows: repeat(3, 1fr);
}
}

@media (min-height: 1081px) {
.grid.horizontal {
grid-template-rows: repeat(4, 1fr);
}
}
</style>
Loading