Build an exceptional Infinite Scroller using Intersection Observer in Vue3

Intersection Observer is an API in JavaScript that allows you to detect when an element enters or leaves a viewport. This can be useful for things like lazy loading images or triggering animations.

In this blog post, we will walk through the process of building a Vue 3 single-page component that wraps the Intersection Observer API and makes it easy to use in our Vue applications.

1. Create the Component

First, we’ll create a new component called InfiniteScrollWrapper.vue. This component will be responsible for wrapping the Intersection Observer API and exposing it to other components in our application.

2. Defining the template

Next, we will define the template for the component. It consists of a wrapper div <div ref="wrapper"></div> , a vue slot to include elements inside the wrapper <slot/> and an ending div<div ref="end"></div> . In this case, we'll be observing the<div ref="end"></div> element.

<template>
    <div ref="wrapper" class="wrapper">
        <slot/>
        <div ref="end" class="scroll_end">
            <span>Load More</span>
        </div>
    </div>
</template>

<script>
import { ref } from "vue"

export default{
  name: 'InfiniteScrollWrapper',

  setup(props, {emits}) {
    const wrapper = ref(null)
    const end = ref(null)

    return {
        end,
        wrapper,
    }
  }
}
</script>

3. Adding the observer

In the setup() function, we'll define a onMounted() lifecycle hook where we will register the observer.

Firstly we create a new IntersectionObserver and assign it to observer variable. The intersection observer takes a function that will take the entries from the observer and check if the entry is intersecting and a reference to the wrapper (root) element. If any entry is intersecting, it will emit an event infinte.

Finally, we’ll observe the element by passing the end element ref to the observer’s observe method in the lifecycle hook.

<template>
    <div ref="wrapper" class="wrapper">
        <slot/>
        <div ref="end" class="scroll_end">
            <span>Load More</span>
        </div>
    </div>
</template>

<script>
import { onMounted, ref } from "vue"

export default{
  name: 'InfiniteScrollWrapper',

  setup(props, {emits}) {
    const wrapper = ref(null)
    const end = ref(null)

    onMounted(() => {
        const observer = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    emit("infinite")
                }
            })
        }, { root: wrapper.value})

        observer.observe(end.value)
    })

    return {
        end,
        wrapper,
    }
  }
}
</script>

4. Using it in a component

Now, we will create a ProductsList component which displays 3 products on intital load and when we scroll adds 3 more components to the view. We will use the InfiniteScrollWrapper component we created above to find when the elements intersect and load more products.

<template>
    <div class="container">
        <InfiniteScrollWrapper class="products_list" :show-more="hasMoreItems" @infinite="loadMore">
            <div v-for="(product, index) in products"
                 :key="index"
                 class="product">
                {{ product }}
            </div>
        </InfiniteScrollWrapper>
    </div>
</template>

<script>
    import { ref } from "vue"
    import InfiniteScrollWrapper from "../Components/InfiniteScrollWrapper.vue"
    export default {
        name: "ProductsList",

        components: {
            InfiniteScrollWrapper,
        },

        setup() {
            const products = ref(["Magazines", "Toothpaste", "Food"])
            const hasMoreItems = ref(true)
            const loadMore = () => {
                if (!hasMoreItems.value) {
                    return
                }

                products.value = [...products.value, "Candy", "Detergent", "Shampoo"]
                hasMoreItems.value = false
            }

            return {
                hasMoreItems,
                products,
                loadMore,
            }
        },
    }
</script>

This is what the actual implementation of the component looks like.

5. Threshold

The threshold accepts a value between 0 and 1 and represents the percentage of the elements that must be visible before isIntersecting becomes true. By default this is set to 0 which means as soon as any part of the element is visible it will be considered intersecting. We can however take it as a prop to the current InfiniteScrollWrapper component.

<template>
    <div ref="wrapper" class="wrapper">
        <slot/>
        <div ref="end" class="scroll_end">
            <span>Load More</span>
        </div>
    </div>
</template>

<script>
export default{
  name: 'InfiniteScrollWrapper',

  props: {
      threshold: { type: Number, default: 0 },
  },

  setup(props, {emits}) {
      const wrapper = ref(null)
      const end = ref(null)

      onMounted(() => {
          const observer = new IntersectionObserver((entries) => {
              entries.forEach(entry => {
                  if (entry.isIntersecting) {
                      emit("infinite")
                  }
              })
          }, { root: wrapper.value, threshold: props.threshold })

          observer.observe(end.value)
      })

      return {
          end,
          wrapper,
      }
  }
}
</script>

Get the Code

You can find the final code for this project in a GitHub repository.

Conclusion

In conclusion, the Intersection Observer API allows for efficient monitoring of elements within the viewport and can help improve the performance and user experience of a web page. This article provides a step-by-step guide on how to use the Intersection Observer API in a Vue.js project, including how to set up the observer, track the intersection of elements, and load additional content as needed.

👏👏 So, I suggest you give your a try on your project and enjoy it!

Feel free to share your thoughts and opinions and leave me a comment if you have any problems or questions.