References

Beginner-friendly references for web development, with live, editable examples.

The JavaScript IntersectionObserver object

Object JavaScript All modern browsers Updated
Quick answer

The IntersectionObserver object efficiently detects when an element enters or leaves the viewport (or a scroll container). new IntersectionObserver(callback).observe(el) runs the callback whenever el's visibility changes. It's the modern, performant way to do lazy loading, infinite scroll and scroll-triggered animations — without slow scroll listeners.

Overview

IntersectionObserver watches whether elements are visible — entering or leaving the viewport — and tells you when that changes. You create an observer with a callback, then call observe(element) on each element you care about. When an observed element scrolls into or out of view, your callback runs, receiving entries whose isIntersecting property says whether each one is now visible.

Its big advantage is performance. The old way — listening to the scroll event and measuring positions with getBoundingClientRect() on every scroll — runs constantly and can make scrolling janky. IntersectionObserver does the work asynchronously and off the main thread, firing only when visibility actually changes. That makes it the right tool for the jobs people used to hack with scroll handlers.

The classic uses: lazy loading images (load them as they approach the viewport), infinite scroll (load more content when a sentinel element appears), and reveal animations (fade elements in as they enter). Options let you tune behavior — a rootMargin to trigger early, and a threshold for how much of the element must be visible. Call unobserve(el) or disconnect() to stop watching once you're done (e.g. after a one-time reveal).

Syntax

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // entry.target just became visible
    }
  });
}, { threshold: 0.1, rootMargin: "0px" });

observer.observe(element);
// later: observer.unobserve(element) or observer.disconnect();

Example

Live example
<div style="height:80px;overflow:auto;border:1px solid #ccc;font:14px system-ui">
  <div style="height:120px;padding:8px">Scroll down...</div>
  <div id="target" style="padding:10px;background:#eee">Watch me</div>
  <div style="height:120px"></div>
</div>
<p id="out" style="font:14px system-ui">not yet seen</p>
<script>
  const box = document.querySelector('div');
  const obs = new IntersectionObserver((entries) => {
    entries.forEach(e => {
      if (e.isIntersecting) document.getElementById('out').textContent = 'Target is visible!';
    });
  }, { root: box, threshold: 1 });
  obs.observe(document.getElementById('target'));
</script>

Best practices

  • Use it instead of scroll listeners for visibility — it's far more performant.
  • Use rootMargin to start loading before an element fully enters the viewport.
  • Set a threshold to control how much of the element must be visible to trigger.
  • Call unobserve() or disconnect() after a one-time effect to free resources.

Frequently asked questions

What is IntersectionObserver used for?
Detecting when elements enter or leave the viewport — for lazy loading images, infinite scroll, and scroll-triggered animations.
Why use IntersectionObserver instead of a scroll event?
Scroll listeners fire constantly and measuring positions each time is slow. IntersectionObserver runs asynchronously and only when visibility changes, so it doesn't hurt scroll performance.
How do I lazy-load images with it?
Observe each image placeholder; in the callback, when entry.isIntersecting is true, set the real src and then unobserve() it.
How do I stop observing an element?
Call observer.unobserve(element) for one element, or observer.disconnect() to stop watching all of them.