Skip to content
This repository was archived by the owner on Nov 5, 2023. It is now read-only.

Commit d586fb2

Browse files
authored
feat: support horizontal scrolling (#141) (#221)
1 parent 444805c commit d586fb2

File tree

9 files changed

+520
-181
lines changed

9 files changed

+520
-181
lines changed

README.md

+8-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ items (e.g. 1000+ items) as a grid in a performant way.
1313
- Just use CSS grid to style your grid. Minimum styling opinions form the
1414
library.
1515
- Support using a paginated API to load the items in the background.
16-
- Support rendering placeholders for unloaded items
16+
- Support rendering placeholders for unloaded items.
17+
- Support both vertical and horizontal scroll.
1718
- Loaded items are cached for better performance.
1819

1920
## Code Examples
@@ -36,7 +37,7 @@ npm install vue-virtual-scroll-grid
3637
| `pageProviderDebounceTime` | Debounce window in milliseconds on the calls to `pageProvider` | `number` | Optional, an integer greater than or equal to 0 |
3738
| `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 |
3839
| `scrollTo` | Scroll to a specific item by index | `number` | Optional, an integer from 0 to the `length` prop - 1 |
39-
| `scrollBehavior` | The behavior of `scrollTo`. Default value is `smooth` | `smooth` | `auto` | Optional, a string to be `smooth` or `auto` |
40+
| `scrollBehavior` | The behavior of `scrollTo`. Default value is `smooth` | `smooth` | `auto` | Optional, a string to be `smooth` or `auto` |
4041

4142
Example:
4243

@@ -113,6 +114,11 @@ Example:
113114
</template>
114115
```
115116

117+
## Scroll Mode
118+
119+
The library uses `grid-auto-flow` CSS property to infer scroll mode. Set it to
120+
`column` value if you want to enable horizontal scroll.
121+
116122
## Caveats
117123

118124
The library does not require items have foreknown width and height, but do

src/Grid.vue

+24-13
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
<template>
2-
<div
3-
v-show="length > 0"
4-
ref="rootRef"
5-
:style="{
6-
height: `${contentHeight}px`,
7-
placeContent: 'start',
8-
}"
9-
>
2+
<div v-show="length > 0" ref="rootRef" :style="rootStyles">
103
<div
114
:style="{
125
opacity: 0,
@@ -40,7 +33,14 @@
4033
</template>
4134

4235
<script lang="ts">
43-
import { defineComponent, onUpdated, PropType, ref } from "vue";
36+
import {
37+
defineComponent,
38+
onUpdated,
39+
PropType,
40+
ref,
41+
computed,
42+
StyleValue,
43+
} from "vue";
4444
import {
4545
fromProp,
4646
fromResizeObserver,
@@ -100,7 +100,7 @@ export default defineComponent({
100100
// data to render
101101
const {
102102
buffer$, // the items in the current scanning window
103-
contentHeight$, // the height of the whole list
103+
contentSize$, // the size of the whole list
104104
scrollAction$, // the value sent to window.scrollTo()
105105
} = pipeline({
106106
// streams of prop
@@ -119,17 +119,28 @@ export default defineComponent({
119119
120120
onUpdated(
121121
once(() => {
122-
scrollAction$.subscribe(([el, top]: ScrollAction) => {
123-
el.scrollTo({ top, behavior: props.scrollBehavior });
122+
scrollAction$.subscribe(({ target, offset }: ScrollAction) => {
123+
target.scrollTo({ ...offset, behavior: props.scrollBehavior });
124124
});
125125
})
126126
);
127127
128+
const contentSize = useObservable(contentSize$);
129+
const rootStyles = computed<StyleValue>(() =>
130+
Object.fromEntries([
131+
...Object.entries(contentSize.value ?? {}).map(([property, value]) => [
132+
property,
133+
value + "px",
134+
]),
135+
["placeContent", "start"],
136+
])
137+
);
138+
128139
return {
129140
rootRef,
130141
probeRef,
131142
buffer: useObservable(buffer$),
132-
contentHeight: useObservable(contentHeight$),
143+
rootStyles,
133144
};
134145
},
135146
});

src/demo/App.vue

+104-47
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,46 @@
11
<template>
2-
<Header />
3-
4-
<Grid
5-
:length="length"
6-
:pageSize="pageSize"
7-
:pageProvider="pageProvider"
8-
:pageProviderDebounceTime="0"
9-
:scrollTo="scrollTo"
10-
:scrollBehavior="scrollBehavior"
11-
:class="$style.grid"
12-
>
13-
<template v-slot:probe>
14-
<ProductItem sizes="(min-width: 768px) 360px, 290px" />
15-
</template>
16-
17-
<template v-slot:placeholder="{ style }">
18-
<ProductItem :style="style" sizes="(min-width: 768px) 360px, 290px" />
19-
</template>
20-
21-
<template v-slot:default="{ item, style }">
22-
<ProductItem
23-
:handle="item.handle"
24-
:price="item.price * 100"
25-
:compare-at-price="item.compare_at_price * 100"
26-
:published-at="new Date(item.published_at)"
27-
:style="style"
28-
:master-src="item.product_image"
29-
:initial-alt-master-src="true"
30-
:alt="item.title"
31-
sizes="(min-width: 768px) 360px, 290px"
32-
:tags="item.tags"
33-
:vendor="item.vendor"
34-
:title="item.title"
35-
/>
36-
</template>
37-
</Grid>
38-
39-
<Control />
2+
<div :class="$style.root">
3+
<Header :class="$style.header" />
4+
5+
<div :class="$style.gridWrapper">
6+
<Grid
7+
:length="length"
8+
:pageSize="pageSize"
9+
:pageProvider="pageProvider"
10+
:pageProviderDebounceTime="0"
11+
:scrollTo="scrollTo"
12+
:scrollBehavior="scrollBehavior"
13+
:class="[$style.grid, $style[scrollMode]]"
14+
>
15+
<template v-slot:probe>
16+
<ProductItem sizes="(min-width: 768px) 360px, 290px" />
17+
</template>
18+
19+
<template v-slot:placeholder="{ style }">
20+
<ProductItem :style="style" sizes="(min-width: 768px) 360px, 290px" />
21+
</template>
22+
23+
<template v-slot:default="{ item, style }">
24+
<ProductItem
25+
:handle="item.handle"
26+
:price="item.price * 100"
27+
:compare-at-price="item.compare_at_price * 100"
28+
:published-at="new Date(item.published_at)"
29+
:style="style"
30+
:master-src="item.product_image"
31+
:initial-alt-master-src="true"
32+
:alt="item.title"
33+
sizes="(min-width: 768px) 360px, 290px"
34+
:tags="item.tags"
35+
:vendor="item.vendor"
36+
:title="item.title"
37+
/>
38+
</template>
39+
</Grid>
40+
</div>
41+
42+
<Control :class="$style.controls" />
43+
</div>
4044
</template>
4145

4246
<script lang="ts">
@@ -45,7 +49,14 @@ import Grid from "../Grid.vue";
4549
import Header from "./Header.vue";
4650
import Control from "./Control.vue";
4751
import ProductItem from "./ProductItem.vue";
48-
import { length, pageSize, pageProvider, scrollTo, scrollBehavior } from "./store";
52+
import {
53+
length,
54+
pageSize,
55+
pageProvider,
56+
scrollMode,
57+
scrollTo,
58+
scrollBehavior
59+
} from "./store";
4960
5061
export default defineComponent({
5162
name: "App",
@@ -54,6 +65,7 @@ export default defineComponent({
5465
length,
5566
pageSize,
5667
pageProvider,
68+
scrollMode,
5769
scrollTo,
5870
scrollBehavior,
5971
}),
@@ -83,61 +95,106 @@ body {
8395
background-color: var(--color-white);
8496
}
8597
98+
.root {
99+
display: grid;
100+
grid-template: "header" "gridWrapper" 1fr "controls";
101+
height: 100vh;
102+
}
103+
104+
.header {
105+
grid-area: header;
106+
}
107+
108+
.gridWrapper {
109+
grid-area: gridWrapper;
110+
height: 100%;
111+
overflow: auto;
112+
}
113+
114+
.controls {
115+
grid-area: controls;
116+
}
117+
86118
.grid {
87119
display: grid;
88120
padding: 0 1rem;
89121
grid-gap: 2rem;
90-
grid-template-columns: repeat(2, 1fr);
91122
place-items: start stretch;
123+
box-sizing: content-box;
124+
}
125+
126+
.grid.vertical {
127+
grid-template-columns: repeat(2, 1fr);
128+
}
129+
130+
.grid.horizontal {
131+
grid-auto-flow: column;
132+
grid-template-columns: 200px;
133+
grid-template-rows: repeat(2, 1fr);
92134
}
93135
94136
@media (min-width: 760px) {
95137
.grid {
96138
padding: 1.5rem;
97139
grid-gap: 3rem;
140+
}
141+
142+
.grid.vertical {
98143
grid-template-columns: repeat(3, 1fr);
99144
}
100145
}
101146
102147
@media (min-width: 1140px) {
103-
.grid {
148+
.grid.vertical {
104149
grid-template-columns: repeat(4, 1fr);
105150
}
106151
}
107152
108153
@media (min-width: 1520px) {
109-
.grid {
154+
.grid.vertical {
110155
grid-template-columns: repeat(5, 1fr);
111156
}
112157
}
113158
114159
@media (min-width: 1900px) {
115-
.grid {
160+
.grid.vertical {
116161
grid-template-columns: repeat(6, 1fr);
117162
}
118163
}
119164
120165
@media (min-width: 2280px) {
121-
.grid {
166+
.grid.vertical {
122167
grid-template-columns: repeat(7, 1fr);
123168
}
124169
}
125170
126171
@media (min-width: 2660px) {
127-
.grid {
172+
.grid.vertical {
128173
grid-template-columns: repeat(8, 1fr);
129174
}
130175
}
131176
132177
@media (min-width: 3040px) {
133-
.grid {
178+
.grid.vertical {
134179
grid-template-columns: repeat(9, 1fr);
135180
}
136181
}
137182
138183
@media (min-width: 3420px) {
139-
.grid {
184+
.grid.vertical {
140185
grid-template-columns: repeat(10, 1fr);
141186
}
142187
}
188+
189+
@media (min-height: 721px) {
190+
.grid.horizontal {
191+
grid-template-rows: repeat(3, 1fr);
192+
}
193+
}
194+
195+
@media (min-height: 1081px) {
196+
.grid.horizontal {
197+
grid-template-rows: repeat(4, 1fr);
198+
}
199+
}
143200
</style>

0 commit comments

Comments
 (0)