IntersectionObserver v2

Interactive API reference, code snippets, and live playground for the IntersectionObserver API.

Constructor

const observer = new IntersectionObserver(callback, options);

Options

OptionTypeDefaultDescription
rootElement | nullnull (viewport)The element used as the viewport for checking visibility.
rootMarginstring"0px"Margin around the root (CSS margin syntax: "10px 20px").
thresholdnumber | number[]0Visibility ratio that triggers the callback. 0=any pixel, 1=fully visible.

Observer Methods

MethodSignatureDescription
observe()observer.observe(target)Start watching a target element.
unobserve()observer.unobserve(target)Stop watching a specific target.
disconnect()observer.disconnect()Stop watching all targets.
takeRecords()observer.takeRecords()Returns pending entries and clears the queue.

Entry Properties

The callback receives an array of IntersectionObserverEntry objects:

callback = (entries, observer) => {
  entries.forEach(entry => {
    entry.isIntersecting;       // boolean
    entry.intersectionRatio;    // 0.0 – 1.0
    entry.boundingClientRect;   // DOMRectReadOnly
    entry.intersectionRect;     // DOMRectReadOnly
    entry.rootBounds;           // DOMRectReadOnly | null
    entry.target;               // Element
    entry.time;                 // DOMHighResTimeStamp
  });
};
PropertyTypeDescription
isIntersectingbooleantrue if the target crosses the threshold into the root.
intersectionRationumberRatio of target visible within the root (0.0–1.0).
boundingClientRectDOMRectReadOnlyTarget's bounding rect relative to the viewport.
intersectionRectDOMRectReadOnlyThe visible portion of the target.
rootBoundsDOMRectReadOnly | nullThe root's bounding rect.
targetElementThe observed element that triggered the entry.
timeDOMHighResTimeStampTimestamp when the intersection changed.

Common Patterns

Lazy Load Images

const images = document.querySelectorAll('img[data-src]');

const imgObserver = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      img.removeAttribute('data-src');
      imgObserver.unobserve(img);
    }
  });
}, { rootMargin: '200px' });

images.forEach(img => imgObserver.observe(img));
<!-- HTML -->
<img data-src="hero.jpg" src="placeholder.svg" alt="Hero">

Infinite Scroll

const sentinel = document.getElementById('sentinel');
let page = 1;

const scrollObserver = new IntersectionObserver(async (entries) => {
  if (entries[0].isIntersecting) {
    page++;
    const items = await fetchMoreItems(page);
    renderItems(items);
  }
}, { rootMargin: '400px' });

scrollObserver.observe(sentinel);
<!-- HTML -->
<div id="item-list">...</div>
<div id="sentinel">Loading...</div>

Sticky Navigation Highlight

const nav = document.querySelector('nav');
const header = document.querySelector('header');

const stickyObserver = new IntersectionObserver(
  ([entry]) => {
    nav.classList.toggle('is-stuck', !entry.isIntersecting);
  },
  { threshold: 0 }
);

stickyObserver.observe(header);
/* CSS */
nav { position: sticky; top: 0; }
nav.is-stuck {
  box-shadow: 0 2px 12px rgba(0,0,0,.3);
  background: rgba(10,10,10,.95);
}

Scroll-Triggered Animations

const animElements = document.querySelectorAll('.animate-on-scroll');

const animObserver = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      entry.target.classList.add('in-view');
      // Uncomment to animate only once:
      // animObserver.unobserve(entry.target);
    } else {
      entry.target.classList.remove('in-view');
    }
  });
}, { threshold: 0.2 });

animElements.forEach(el => animObserver.observe(el));
/* CSS */
.animate-on-scroll {
  opacity: 0;
  transform: translateY(30px);
  transition: opacity .6s ease, transform .6s ease;
}
.animate-on-scroll.in-view {
  opacity: 1;
  transform: translateY(0);
}

Live Playground

Scroll the container below. Adjust options and observe the callback output in real-time.

↓ Scroll down to find targets ↓
Target A
Target B
Target C

Generated Code

`; const blob = new Blob([html], { type: 'text/html' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'intersection-observer-demo.html'; a.click(); URL.revokeObjectURL(a.href); } function copyAllPatterns() { const all = document.querySelectorAll('.tab-panel pre code'); const text = Array.from(all).map(c => c.textContent).join('\n\n// ─────────────\n\n'); navigator.clipboard.writeText(text); alert('All pattern snippets copied to clipboard!'); }