/ Gists / Intersection observer (infinity scroll)
On gists

Intersection observer (infinity scroll)

JavaScript Web Api

demo.js Raw #

// https://levelup.gitconnected.com/what-is-intersection-observer-api-and-how-is-it-useful-1e91a14579df
// https://codepen.io/ohdylan/pen/ZExqKzx



let secondLastChild = document.querySelectorAll('.box:nth-last-child(2)')[0];
let boxes = document.querySelector('.boxes')
const mockFetchMoreBoxes = () => {
    const startIndex = parseInt(secondLastChild.textContent.replace('Test Element', '')) + 2
    for(let i = startIndex; i < startIndex + 20; i++) {
        const newBox = document.createElement('div')
        newBox.textContent = `Test Element ${i}`
        newBox.classList.add('box')
        boxes.append(newBox)
        if(i == startIndex + 18) {
            observer.unobserve(secondLastChild)
            secondLastChild = newBox
            observer.observe(secondLastChild);
        }
    }
}

let observer = new IntersectionObserver((entries) => {
    console.log(entries[0])
    const secondLastChild = entries[0]
    if(secondLastChild.isIntersecting){
        return mockFetchMoreBoxes()
    }
}, {})

observer.observe(secondLastChild);

demo.html Raw #

<div class="boxes">
  <div class="box">Test Element 1</div>
  <div class="box">Test Element 2</div>
  <div class="box">Test Element 3</div>
  <div class="box">Test Element 4</div>
  <div class="box">Test Element 5</div>
  <div class="box">Test Element 6</div>
  <div class="box">Test Element 7</div>
  <div class="box">Test Element 8</div>
  <div class="box">Test Element 9</div>
  <div class="box">Test Element 10</div>
  <div class="box">Test Element 11</div>
  <div class="box">Test Element 12</div>
  <div class="box">Test Element 13</div>
  <div class="box">Test Element 14</div>
  <div class="box">Test Element 15</div>
  <div class="box">Test Element 16</div>
</div>

demo2.js Raw #

// 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>