// https://mayank-jain.medium.com/infinite-scrolling-using-vue3-and-intersectionobserver-e767c58c4bd3

// src/components/InfiniteScroller.vue
<script setup>
import { ref, onMounted } from "vue";
const emits = defineEmits(['infinite']);
let scroller = ref(null);
let endOfScroller = ref(null);
onMounted(() => {
  const observer = new IntersectionObserver((entries) => {
    let entry = entries[0];
    if(entry.isIntersecting) {
        emits('infinite');
    }
  }, { root: scroller.value });
  observer.observe(endOfScroller.value);
});
</script>

<template>
  <div ref="scroller" class="scroller">
    <slot></slot>
    <div ref="endOfScroller"></div>
  </div>
</template>

<style scoped>
.scroller {
  height: 100%;
  width: 100%;
  overflow: auto;
}
</style>


// src/components/Card.vue
<script setup>
defineProps({
  value: Number,
});
</script>

<template>
  <div class="card">{{ value }}</div>
</template>

<style scoped>
.card {
  height: 10rem;
  width: 10rem;
  padding: 2rem;
  color: white;
  background-color: indigo;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 3rem;
}
</style>


// src/components/ Cards.vue
<script setup>
import { ref } from "vue";
import Card from "./Card.vue";
import InfiniteScroller from "./InfiniteScroller.vue";
let items = ref([]);
let limit = 100;
let offset = 0;
const loadItems = async () => {
  const newItems = await generateData(limit, offset);
  items.value = [...items.value, ...newItems];
  ++offset;
};
const generateData = (limit, offset, delay = 1000) => {
  return new Promise((resolve) => {
    const newData = Array(limit)
      .fill(0)
      .map((_, index) => index + offset * limit + 1);
    setTimeout(resolve, delay, newData);
  });
};
</script>

<template>
  <InfiniteScroller class="cards" @infinite="loadItems">
    <Card v-for="item in items" :value="item" :key="item"></Card>
  </InfiniteScroller>
</template>

<style scoped>
.cards {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
  justify-content: center;
  padding: 1rem;
  box-sizing: border-box;
}
</style>