Interactive API reference, code snippets, and live playground for the IntersectionObserver API.
const observer = new IntersectionObserver(callback, options);
| Option | Type | Default | Description |
|---|---|---|---|
root | Element | null | null (viewport) | The element used as the viewport for checking visibility. |
rootMargin | string | "0px" | Margin around the root (CSS margin syntax: "10px 20px"). |
threshold | number | number[] | 0 | Visibility ratio that triggers the callback. 0=any pixel, 1=fully visible. |
| Method | Signature | Description |
|---|---|---|
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. |
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
});
};
| Property | Type | Description |
|---|---|---|
isIntersecting | boolean | true if the target crosses the threshold into the root. |
intersectionRatio | number | Ratio of target visible within the root (0.0–1.0). |
boundingClientRect | DOMRectReadOnly | Target's bounding rect relative to the viewport. |
intersectionRect | DOMRectReadOnly | The visible portion of the target. |
rootBounds | DOMRectReadOnly | null | The root's bounding rect. |
target | Element | The observed element that triggered the entry. |
time | DOMHighResTimeStamp | Timestamp when the intersection changed. |
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">
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>
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);
}
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);
}
Scroll the container below. Adjust options and observe the callback output in real-time.