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>