Introduction to Intersection Observer With Lazy Loading of Comments

12/6/2020

We use intersection observer to know when an element intersect within a defined viewport or any ancestor element which we specify. As IntersectionObserver react asynchronously when the “target” element intersect the viewport or ancestor element.

Without intersection observer, intersection detection required event handlers and loops and calling methods like Element.getBoundingClientRect() to build required information for every element affected. As this is a resource heavy operation and the events react synchronously on main thread, it will impact the experience of user.

You can use intersection observer to improve the experience and performance of user by

  • lazy loading images
  • implementing infinite scrolling
  • doing resource heavy task only when a user needs them like loading disqus comment on reaching the end of article

Before we go further with lazy loading of disqus comment with intersection observer, lets understand about it’s options.

Root is the viewport for checking visibility of target element. Target element is the element we want know if it is intersecting in the defined root. We also have rootMargin to define the margin around root, like the CSS margin property. By default it is set to 0. We use threshold option to define at what visibility of target element in a intersection area show we react. It takes an array of values, each from 0 and 1. For example for threshold 0, the observer will react when the target element is just entering the viewport of root. Observer also react when the observed target element just leaves the viewport of root completely.

Lazy loading of Comments

We need to identify when comment container is close to current visible viewport, so that only then we will load it, instead of loading all at initial load. As the current viewport is the browser’s viewport, we will set it to null. Leaving it also blank works same. As we need to identify when the target element just enters the root we will set threshold to 0. Also we need to load the comments little bit before reaching the comment’s section so that we can avoid/reduce the delay before the user see the comment.

const options = {
  root: null,
  rootMargin: "0% 0% 100% 0%",
  threshold: 0
}

We want to create the intersection observer on the first render of component only, so we write the code for that in useEffect(callback, []). We will pass a callback function and option to create it.

As we want to load the disqus only at the first time the target intersect the root, we will need to unobserve the target. After creating the intersection observer we will observe the target.

const target = window.document.getElementById("comment__container")
const callback = function(entries, observer) {
  if (entries && entries[0] && entries[0].isIntersecting) {
    observer.unobserve(target)
    setLoading(true)
  }
}
const observer = new IntersectionObserver(callback, options)
observer.observe(target)

You can view the full code for the comment component with lazy loading as follows

import React, { useEffect, useState } from "react"
import { Disqus } from "gatsby-plugin-disqus"
import styled from "@emotion/styled"

const Comment = ({ config }) => {
  const [isLoaded, setLoading] = useState(false)
  useEffect(() => {
    if (window && !window.document.getElementById("dsq-embed-scr")) {
      const target = window.document.getElementById("comment__container")
      const options = {
        root: null,
        rootMargin: "0% 0% 100% 0%",
        threshold: 0
      }
      const callback = function(entries, observer) {
        if (entries && entries[0] && entries[0].isIntersecting) {
          observer.unobserve(target)
          setLoading(true)
        }
      }
      const observer = new IntersectionObserver(callback, options)
      observer.observe(target)
    }
  }, [])

  return (
    <CommentContainer id="comment__container">
      {isLoaded ? <Disqus config={config} /> : <p>Loading Comment...</p>}
    </CommentContainer>
  )
}

const CommentContainer = styled.div`
  display: block;
`

export default Comment

We also need to check for window as Gatsby also does SSR, during which we don’t want to load this code. As you can see I am using gatsby-plugin-disqus for loading disqus on my site.

As not all browsers today support IntersectionObserver, we need to add intersection-observer polyfill by adding the following code in gatsby-browser.js.

exports.onClientEntry = async () => {
  if (typeof IntersectionObserver === "undefined") {
    await import("intersection-observer")
  }
}
© 2025 Joel Jacob