Infinite Scroll
Infinite Scroll with Conveyer.
The conveyer can be used in combination with other components.
- The InfiniteScroll module uses InfiniteGrid.
- If it is combined with other components, it is difficult to get a ref. You can initialize it by setting autoInit to false and setting the ref directly.
- JavaScript
- React
- Vue@2
- Vue@3
- Angular
- Svelte
<div class="examples">
<div class="buttons">
<button class="prev">prev</button>
<button class="next">next</button>
</div>
<div class="items"></div>
</div>
import { MasonryInfiniteGrid } from "@egjs/infinitegrid";
import Conveyer from "@egjs/conveyer";
const grid = new MasonryInfiniteGrid(".items", {
container: true,
gap: 5,
autoInit: false,
});
grid.on("renderComplete", () => {
// conveyer.updateItems();
conveyer.setItems(grid.getItems().map((item) => ({
element: item.element,
pos: item.cssContentPos || item.contentPos,
size: item.contentSize,
})));
conveyer.updateContainer();
});
let i = 0;
grid.on("requestAppend", () => {
++i;
grid.append(`<div class="item">${i}</div>`)
});
const conveyer = new Conveyer(".items", {
preventClickOnDrag: true,
horizontal: false,
itemSelector: ".item",
});
const prev = document.querySelector(".prev");
const next = document.querySelector(".next");
// subscribe reactive properties
conveyer.subscribe("isReachStart", (isReachStart) => {
prev.disabled = isReachStart;
});
conveyer.subscribe("isReachEnd", (isReachEnd) => {
next.disabled = isReachEnd;
});
// scrollIntoView
prev.addEventListener("click", () => {
// start to end
conveyer.scrollIntoView("start", {
align: "end",
duration: 500,
excludeStand: true,
});
});
next.addEventListener("click", () => {
// end to start
conveyer.scrollIntoView("end", {
align: "start",
duration: 500,
excludeStand: true,
});
});
grid.renderItems();
conveyer.init();
import * as React from "react";
import { useConveyer } from "@egjs/react-conveyer";
import { MasonryInfiniteGrid } from "@egjs/react-infinitegrid";
function getItems(nextGroupKey: number, count: number) {
const nextItems = [];
const nextKey = nextGroupKey * count;
for (let i = 0; i < count; ++i) {
nextItems.push({ groupKey: nextGroupKey, key: nextKey + i });
}
return nextItems;
}
export default function InfiniteScroll() {
const gridRef = React.useRef<MasonryInfiniteGrid>();
const ref = React.useRef<HTMLElement>();
const [items, updateItems] = React.useState(() => getItems(0, 10));
const {
isReachStart,
isReachEnd,
setItems,
updateContainer,
scrollIntoView,
init,
} = useConveyer(ref, {
horizontal: false,
autoInit: false,
});
React.useEffect(() => {
ref.current = gridRef.current.getScrollContainerElement();
init();
});
return <div className="examples">
<div className="buttons">
<button className="prev" disabled={isReachStart} onClick={() => {
// start to end
scrollIntoView("start", {
align: "end",
duration: 500,
excludeStand: true,
});
}}>Prev</button>
<button className="next" disabled={isReachEnd} onClick={() => {
// end to start
scrollIntoView("end", {
align: "start",
duration: 500,
excludeStand: true,
});
}}>Next</button>
</div>
<MasonryInfiniteGrid className="items infinite" container={true} gap={5}
ref={gridRef}
onRequestAppend={e => {
const nextGroupKey = (+e.groupKey! || 0) + 1;
updateItems([
...items,
...getItems(nextGroupKey, 10),
]);
}} onRenderComplete={e => {
setItems(e.currentTarget.getItems().map((item) => ({
element: item.element,
pos: item.computedContentPos,
size: item.contentSize,
})));
updateContainer();
}}>
{items.map((item, i) => (<div className={`item item${i % 4}`} data-grid-groupkey={item.groupKey} key={item.key}>{item.key}</div>))}
</MasonryInfiniteGrid>
</div>;
}
<template>
<div class="examples">
<div class="buttons">
<button class="prev" :disabled="isReachStart" @click="prev">prev</button>
<button class="next" :disabled="isReachEnd" @click="next">next</button>
</div>
<masonry-infinite-grid
class="items"
v-bind:gap="5"
ref="ig"
v-on:request-append="onRequestAppend"
>
<div
class="item"
v-for="item in items"
:key="item.key"
:data-grid-groupkey="item.groupKey"
>{item.key}</div>
</masonry-infinite-grid>
</div>
</template>
<script lang="ts">
import { MasonryInfiniteGrid } from "@egjs/vue-infinitegrid";
import { useConveyer } from "@egjs/vue2-conveyer";
export default {
components: {
MasonryInfiniteGrid,
},
setup() {
const { ref, scrollIntoView, isReachStart, isReachEnd, updateContainersetItems, init } = useConveyer({
horizontal: false,
});
return {
ref,
scrollIntoView,
isReachStart,
isReachEnd,
updateContainer,
setItems,
init,
};
},
data() {
return {
items: this.getItems(0, 10),
};
},
mounted() {
this.ref.value = this.ig.getScrollContainerElement();
this.init();
}
methods: {
getItems(nextGroupKey: nextGroupKey, count: number) {
const nextItems: any[] = [];
for (let i = 0; i < count; ++i) {
const nextKey = nextGroupKey * count + i;
nextItems.push({ groupKey: groupKey, key: nextKey });
}
return nextItems;
},
onRequestAppend(e) {
const nextGroupKey = (+e.groupKey! || 0) + 1;
this.items = [...this.items, ...this.getItems(nextGroupKey, 10)];
},
onRenderComplete(e) {
this.setItems(e.currentTarget.getItems().map((item) => ({
element: item.element,
pos: item.computedContentPos,
size: item.contentSize,
})));
this.updateContainer();
},
prev() {
this.scrollIntoView("start", {
align: "end",
duration: 500,
excludeStand: true,
});
},
next() {
this.scrollIntoView("end", {
align: "start",
duration: 500,
excludeStand: true,
});
},
},
};
</script>
<template>
<div class="examples">
<div class="buttons">
<button class="prev" :disabled="isReachStart" @click="prev">prev</button>
<button class="next" :disabled="isReachEnd" @click="next">next</button>
</div>
<masonry-infinite-grid
class="items"
v-bind:gap="5"
ref="ig"
v-on:request-append="onRequestAppend"
>
<div
class="item"
v-for="item in items"
:key="item.key"
:data-grid-groupkey="item.groupKey"
>{item.key}</div>
</masonry-infinite-grid>
</div>
</template>
<script lang="ts">
import { MasonryInfiniteGrid } from "@egjs/vue3-infinitegrid";
import { useConveyer } from "@egjs/vue-conveyer";
export default {
components: {
MasonryInfiniteGrid,
},
setup() {
const { ref, scrollIntoView, isReachStart, isReachEnd, updateContainersetItems, init } = useConveyer({
horizontal: false,
});
return {
ref,
scrollIntoView,
isReachStart,
isReachEnd,
updateContainer,
setItems,
init,
};
},
data() {
return {
items: this.getItems(0, 10),
};
},
mounted() {
this.ref.value = this.ig.getScrollContainerElement();
this.init();
}
methods: {
getItems(nextGroupKey: nextGroupKey, count: number) {
const nextItems: any[] = [];
for (let i = 0; i < count; ++i) {
const nextKey = nextGroupKey * count + i;
nextItems.push({ groupKey: groupKey, key: nextKey });
}
return nextItems;
},
onRequestAppend(e) {
const nextGroupKey = (+e.groupKey! || 0) + 1;
this.items = [...this.items, ...this.getItems(nextGroupKey, 10)];
},
onRenderComplete(e) {
this.setItems(e.currentTarget.getItems().map((item) => ({
element: item.element,
pos: item.computedContentPos,
size: item.contentSize,
})));
this.updateContainer();
},
prev() {
this.scrollIntoView("start", {
align: "end",
duration: 500,
excludeStand: true,
});
},
next() {
this.scrollIntoView("end", {
align: "start",
duration: 500,
excludeStand: true,
});
},
},
};
</script>
<div class="examples">
<div class="buttons">
<button class="prev" [disabled]="conveyer?.isReachStart" (click)="prev()">
prev
</button>
<button class="next" [disabled]="conveyer?.isReachEnd" (click)="next()">
next
</button>
</div>
<div
NgxMasonryInfiniteGrid
class="items"
[gap]="5"
[align]="'justify'"
[items]="items"
[trackBy]="trackBy"
[groupBy]="groupBy"
(requestAppend)="onRequestAppend($event)"
(renderComplete)="onRenderComplete($event)"
[horizontal]="false"
[container]="true"
[ngxConveyer]="{ horizontal: false }"
#conveyer="ngxConveyer"
#ig
>
<div class="item" *ngFor="let item of ig.visibleItems; trackBy: trackBy;">
{{item.key}}
</div>
</div>
</div>
import { Component, ViewChild } from '@angular/core';
import { NgxInfiniteGridComponent } from '@egjs/ngx-infinitegrid';
import { OnRequestAppend, OnRenderComplete } from '@egjs/infinitegrid';
import { NgxConveyerDirective } from '@egjs/ngx-conveyer';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: [
"./app.component.css",
],
})
export class AppComponent {
@ViewChild('ig', { static: false }) ig!: NgxInfiniteGridComponent;
@ViewChild('conveyer', { static: false }) conveyer!: NgxConveyerDirective;
items = this.getItems(0, 10);
getItems(nextGroupKey: number, count: number) {
const nextItems = [];
const nextKey = nextGroupKey * count;
for (let i = i; i < count; ++i) {
nextItems.push({ groupKey: nextGroupKey, key: nextKey + i });
}
return nextItems;
}
groupBy(_: any, item: any) {
return item.groupKey;
}
trackBy(_: any, item: any) {
return item.key;
}
onRequestAppend(e: OnRequestAppend) {
const nextGroupKey = (+e.groupKey! || 0) + 1;
this.items = [
...this.items,
...this.getItems(nextGroupKey, 10),
];
}
onRenderComplete(e: OnRenderComplete) {
this.conveyer.setItems(e.currentTarget.getItems().map((item) => ({
element: item.element,
pos: item.computedContentPos,
size: item.contentSize,
})));
this.conveyer.updateContainer();
}
prev() {
this.conveyer.scrollIntoView("start", {
align: "end",
duration: 500,
excludeStand: true,
});
}
next() {
this.conveyer.scrollIntoView("end", {
align: "start",
duration: 500,
excludeStand: true,
});
}
}
<script>
import { MasonryInfiniteGrid } from "@egjs/svelte-infinitegrid";
import { useConveyer } from "@egjs/svelte-conveyer";
import { onMount } from "svelte";
let ig;
let items = getItems(0, 10);
function getItems(nextGroupKey, count) {
const nextItems = [];
for (let i = i; i < count; ++i) {
const nextKey = nextGroupKey * count + i;
nextItems.push({ groupKey: nextGroupKey, key: nextKey });
}
return nextItems;
}
const {
isReachStart,
isReachEnd,
setItems,
updateContainer,
scrollIntoView,
ref,
init,
} = useConveyer({
horizontal: false,
autoInit: false,
});
onMount(() => {
ref.current = ig.getScrollContainerElement();
init();
})
</script>
<div class="examples">
<div className="buttons">
<button className="prev" disabled={$isReachStart} on:click={() => {
// start to end
scrollIntoView("start", {
align: "end",
duration: 500,
excludeStand: true,
});
}}>Prev</button>
<button className="next" disabled={$isReachEnd} on:click={() => {
// end to start
scrollIntoView("end", {
align: "start",
duration: 500,
excludeStand: true,
});
}}>Next</button>
</div>
<MasonryInfiniteGrid
class="items"
gap={5}
{items}
on:requestAppend={({ detail: e }) => {
const nextGroupKey = (+e.groupKey || 0) + 1;
items = [...items, ...getItems(nextGroupKey, 10)];
}}
on:renderComplete={({ detail: e }) => {
setItems(e.currentTarget.getItems().map((item) => ({
element: item.element,
pos: item.computedContentPos,
size: item.contentSize,
})));
updateContainer();
}}
bind:this={ig}
let:visibleItems
>
{#each visibleItems as item (item.key)}
<div class="item">${item.key}</div>
{/each}
</MasonryInfiniteGrid>
</div>