← Back to DevBytes

Implementing IntersectionObserver v2 in Modern Web Applications

Introduction to IntersectionObserver v2

IntersectionObserver v2 builds on the original IntersectionObserver API to deliver deeper visibility insights. While v1 tells you when an element enters or exits the viewport, v2 adds the ability to know if the element is actually visible to the user — taking into account occlusions, CSS transforms, opacity, and more. This tutorial covers everything you need to implement v2 in modern web applications.

What Is IntersectionObserver v2?

IntersectionObserver v2 is an extension of the original IntersectionObserver specification. The core addition is the isVisible property on each IntersectionObserverEntry. When you configure an observer with the trackVisibility option, the browser will compute a boolean indicating whether the observed element is currently “visible” from the user’s perspective. This computation accounts for:

The v2 API introduces two new configuration options:

Why It Matters: Enhanced Visibility Insights

Standard IntersectionObserver (v1) only tells you whether an element intersects the viewport. An element could be inside the viewport but completely invisible because:

For many use cases — ad impression tracking, analytics, lazy-loading UI components, and more — you need to know whether the user can actually see the element. v2 fills that gap. It enables:

Core Concepts and API

The v2 API remains largely consistent with v1. The key differences are the constructor options and the isVisible property. Here’s a quick comparison:

Important: isVisible is only populated when trackVisibility is true. If you do not set that option, isVisible will be undefined.

Implementing IntersectionObserver v2: Step-by-Step

1. Feature Detection

Not all browsers support v2 yet. Before using it, check whether the browser’s IntersectionObserver implementation includes the trackVisibility capability. A robust detection method is to attempt constructing an observer with the option and catch errors:

function supportsIntersectionObserverV2() {
  if (!('IntersectionObserver' in window)) return false;

  try {
    // Create a dummy observer with trackVisibility
    new IntersectionObserver(() => {}, { trackVisibility: true, delay: 100 });
    return true;
  } catch (e) {
    return false;
  }
}

if (supportsIntersectionObserverV2()) {
  console.log('v2 is supported!');
} else {
  console.log('Fall back to v1 or other visibility checks.');
}

You can also check for the prototype property if you prefer a non-try/catch approach, but the above covers edge cases like missing constructor options gracefully.

2. Configuring the Observer

Create an observer with both v1 and v2 options. The delay option helps throttle the visibility computations. A typical setup:

const observerOptions = {
  root: null,             // viewport
  rootMargin: '0px',
  threshold: 0,          // fire as soon as 1px intersects (or use [0, 1])
  trackVisibility: true, // enable v2 visibility tracking
  delay: 100             // minimum 100ms between visibility evaluations
};

const observer = new IntersectionObserver(onIntersection, observerOptions);

3. Handling Callbacks

The callback receives an array of IntersectionObserverEntry objects. Each entry now contains:

A common pattern is to combine both properties for a more nuanced understanding:

function onIntersection(entries) {
  entries.forEach(entry => {
    const { target, isIntersecting, isVisible } = entry;

    // Element is in the viewport but might be occluded
    if (isIntersecting) {
      if (isVisible === true) {
        console.log(`${target.id} is visible to the user.`);
        // Trigger expensive operations here
      } else if (isVisible === false) {
        console.log(`${target.id} is in viewport but hidden (occluded, opacity 0, etc.).`);
        // Defer work or clean up
      } else {
        // isVisible undefined – v2 not supported or not yet computed
        console.log(`${target.id} is intersecting, visibility unknown.`);
      }
    } else {
      console.log(`${target.id} is outside the viewport.`);
      // Pause work, free resources
    }
  });
}

4. Practical Example: Lazy-Loading Images with Visibility Tracking

Let’s build a lazy-loading image component that loads images only when they are both intersecting the viewport and truly visible to the user. This avoids loading images that are hidden behind a modal or an overlay.

<!-- HTML -->
<img data-src="photo.jpg" class="lazy-img" alt="A lazy-loaded photo">
<div class="overlay-banner">Temporary promotional banner</div>

<style>
  .lazy-img { width: 300px; height: 200px; background: #eee; }
  .overlay-banner {
    position: fixed; top: 0; left: 0; width: 100%; height: 60px;
    background: rgba(0,0,0,0.8); z-index: 999; color: white; display: flex; align-items: center;
  }
</style>

<script>
  // Feature detection and fallback
  const supportsV2 = (() => {
    if (!('IntersectionObserver' in window)) return false;
    try {
      new IntersectionObserver(() => {}, { trackVisibility: true, delay: 100 });
      return true;
    } catch (e) { return false; }
  })();

  const observerOptions = {
    rootMargin: '200px',  // pre-load margin
    threshold: 0,
    ...(supportsV2 && { trackVisibility: true, delay: 100 })
  };

  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      const img = entry.target;
      const shouldLoad = entry.isIntersecting &&
        (supportsV2 ? entry.isVisible !== false : true); // fallback: assume visible if no v2

      if (shouldLoad && img.dataset.src) {
        img.src = img.dataset.src;
        img.removeAttribute('data-src');
        observer.unobserve(img); // stop observing once loaded
      }
    });
  }, observerOptions);

  document.querySelectorAll('.lazy-img').forEach(img => observer.observe(img));
</script>

In this example, images under a fixed overlay banner will not load until the banner is dismissed (or the user scrolls past it, revealing the images). The fallback gracefully handles browsers without v2 by simply loading on intersection.

Best Practices

Use v2 When You Need Deeper Visibility Information

Don’t enable trackVisibility indiscriminately. It triggers additional browser calculations (layout, style, and layer tree inspections). Reserve it for cases where knowing the true visibility adds value — ad tracking, analytics, critical lazy-loading, or performance optimizations that depend on actual screen presence.

Combine with v1 for Full Coverage

Always check isIntersecting alongside isVisible. An element can be outside the viewport (non-intersecting) and isVisible may be true in certain edge cases (e.g., when the element is partially off-screen but the visible part is considered “visible” by the algorithm). Use both to form a complete picture.

Avoid Over-Observing

Observing too many elements with trackVisibility: true can cause performance bottlenecks. Use the delay option to throttle updates. Consider grouping observations or only observing elements that are likely to be visible (e.g., after they start intersecting). Unobserve elements as soon as you no longer need the data.

Handle Callback Performance

The callback runs on every visibility change. Keep it lightweight — avoid heavy DOM manipulations, synchronous style reads, or complex state updates. If you need to react, consider batching updates using requestAnimationFrame or scheduling tasks with setTimeout(0).

Respect User Privacy

Browsers enforce privacy restrictions on v2. The isVisible computation may be disabled when:

You might receive entries where isVisible is undefined even if trackVisibility is enabled. Always code defensively and treat undefined as “unknown visibility.” Never rely on isVisible for security or payment logic — it’s an optimization hint, not a guarantee.

Conclusion

IntersectionObserver v2 adds a crucial layer of visibility intelligence to modern web applications. By telling you not just whether an element intersects the viewport, but whether it’s truly visible to the user, it unlocks more accurate ad tracking, smarter lazy-loading, and deeper performance optimizations. Implement feature detection, combine isIntersecting with isVisible, respect the performance trade-offs, and always handle privacy-driven “unknown” states. With these techniques, you can deliver faster, more user-centric experiences while gathering better analytics.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles