Frontend Development 11 min read

Understanding and Using IntersectionObserver for Lazy Loading, Scroll Animations, Infinite Scrolling, and Virtual Lists

This article introduces the IntersectionObserver API, explains its constructor, options, and entry properties, and demonstrates practical applications such as lazy loading images, scroll‑triggered animations, infinite scrolling, and virtual list rendering with complete code examples.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Understanding and Using IntersectionObserver for Lazy Loading, Scroll Animations, Infinite Scrolling, and Virtual Lists

Introduction

Historically, implementing lazy loading, scroll animations, and similar effects required manual scroll event handling, offset calculations, and debouncing. Modern browsers provide observation APIs that simplify and improve performance when tracking an element’s intersection with the viewport.

Overview of IntersectionObserver

The IntersectionObserver API creates an observer object that monitors the intersection between a target element and a root (usually the viewport). When the intersection ratio crosses specified thresholds, a callback receives detailed data about the change.

API

Constructor

The constructor accepts two arguments:

callback : Function executed when intersection changes.

options (optional): Configuration object.

The observer instance provides four methods:

observe(element) – start observing a target.

unobserve(element) – stop observing a target.

disconnect() – disconnect the observer.

takeRecords() – return an array of IntersectionObserverEntry objects.

const myObserver = new IntersectionObserver(callback, options);
myObserver.observe(element);
myObserver.unobserve(element);
myObserver.disconnect();

Constructor Parameters

- callback

The callback receives entries (an array of IntersectionObserverEntry ) and the observer instance.

- options

Options allow customizing the root, rootMargin, and threshold list.

Property

Description

root

Ancestor element used as the viewport; defaults to the document viewport.

rootMargin

Margin around the root for expanding or shrinking the intersection area (e.g., "0px 0px 0px 0px").

threshold

Sorted list of ratios (0‑1) that trigger the callback when crossed; default is 0.

IntersectionObserverEntry

Property

Description

boundingClientRect

Bounding box of the target element (same as

element.getBoundingClientRect()

).

intersectionRatio

Proportion of the target visible in the root.

intersectionRect

Rectangle describing the intersecting area.

isIntersecting

Boolean indicating whether the target is currently intersecting.

rootBounds

Bounding box of the root.

target

The observed element.

time

Timestamp when the intersection occurred.

Applications

Lazy Loading

Store the real image URL in data-src and replace src when the element becomes visible.

<div class="skin_img">
  <img class="lazyload" data-src="//example.com/image.jpg" alt="Example" />
</div>

.skin_img { margin-bottom:20px; width:auto; height:500px; overflow:hidden; position:relative; }
const imgList = [...document.querySelectorAll('img')];
const observer = new IntersectionObserver((entries) => {
  entries.forEach(item => {
    if (item.isIntersecting) {
      console.log(item.target.dataset.src);
      item.target.src = item.target.dataset.src;
      observer.unobserve(item.target);
    }
  });
}, { root: document.querySelector('.root') });
imgList.forEach(img => observer.observe(img));

Scroll Animation

Add animation classes when elements intersect the viewport.

const elements = document.querySelectorAll('.observer-item');
const observer = new IntersectionObserver(callback);
elements.forEach(ele => {
  ele.classList.add('opaque');
  observer.observe(ele);
});

function callback(entries, instance) {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const element = entry.target;
      element.classList.remove('opaque');
      element.classList.add('come-in');
      instance.unobserve(element);
    }
  });
}

/* CSS */
.come-in { opacity:1; transform:translateY(0); animation:come-in 1s ease forwards; }
@keyframes come-in { 100% { transform:translateY(0); } }

Infinite Scrolling

Observe a sentinel element ( lastContentRef ) and load more data when it becomes visible.

const [list, setList] = useState(new Array(10).fill(null));
const [loading, setLoading] = useState(false);
const lastContentRef = useRef(null);

const loadMore = async () => {
  if (timer) return;
  setLoading(true);
  await new Promise(resolve => timer = setTimeout(() => resolve(timer = null), 1500));
  setList(prev => [...prev, ...new Array(10).fill(null)]);
  setLoading(false);
};

useEffect(() => {
  const io = new IntersectionObserver((entries) => {
    if (entries[0]?.isIntersecting && !loading) loadMore();
  });
  lastContentRef?.current && io.observe(lastContentRef.current);
}, []);

Virtual List

Use rootMargin to create a buffer zone and render only items that intersect the viewport, replacing off‑screen items with placeholders.

<template v-for="(item, idx) in listData" :key="item.id">
  <div class="content-item" :data-index="idx">
    <template v-if="item.visible">
      {{ item.value }}
    </template>
  </div>
</template>

_entries.forEach(row => {
  const index = row.target.dataset.index;
  if (!row.isIntersecting) {
    row.target.style.height = `${row.target.clientHeight}px`;
    listData.value[index].visible = false;
  } else {
    row.target.style.height = '';
    listData.value[index].visible = true;
  }
});

Compatibility

Most modern browsers support IntersectionObserver; only legacy Internet Explorer lacks native support.

Conclusion

IntersectionObserver provides a concise, performant way to monitor element‑viewport intersections, enabling lazy loading, scroll‑based animations, infinite scrolling, virtual lists, analytics, parallax effects, and auto‑play scenarios. Its low overhead and straightforward API make it a valuable tool for front‑end developers.

IntersectionObserverlazy-loadingWeb APIvirtual-listScroll Animation
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.