Intersection Observer
This lesson explains Intersection Observer with clear examples, use cases, and best practices so you can use HTML5 APIs confidently in real web applications.
HTML5 Intersection Observer API Overview
The HTML5 Intersection Observer API allows web applications to detect when
an element enters or leaves the visible area of a page, scroll container,
or viewport. It is a modern, efficient alternative to repeatedly checking
element positions during scroll events.
Intersection Observer is commonly used for lazy loading images, infinite
scrolling, scroll-based animations, advertisement visibility tracking,
analytics impressions, active section highlighting, video autoplay when
visible, and performance-friendly UI updates.
| Feature | Description |
| API Name | Intersection Observer API |
| Main Object | IntersectionObserver |
| Main Method | observe() |
| Stop Method | unobserve() or disconnect() |
| Best Use | Detect element visibility without scroll performance issues. |
| Common Uses | Lazy loading, infinite scroll, animations, tracking, navigation. |
Basic Intersection Observer Example
// HTML5 Intersection Observer API
const target =
document.querySelector("#target");
const observer =
new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
console.log("Element is visible.");
} else {
console.log("Element is not visible.");
}
});
});
observer.observe(target);
This example watches a target element and runs a callback whenever the
element intersects with the viewport.
How Intersection Observer Works
The browser observes one or more target elements and calls your callback
when their visibility changes relative to a root area.
| Step | Description | Example Syntax |
| Create Observer | Create a new observer with callback and options. | new IntersectionObserver(callback, options) |
| Select Target | Choose the element to observe. | document.querySelector("#target") |
| Start Watching | Observe the selected element. | observer.observe(target) |
| Receive Entry | Read visibility information from each entry. | entry.isIntersecting |
| Stop Watching | Stop observing one element or all elements. | observer.unobserve(target) |
Intersection Observer Options
const options = {
root: null,
rootMargin: "0px",
threshold: 0.5
}
const observer =
new IntersectionObserver(
callback,
options
);
| Option | Description | Example Value |
root | Element used as the viewing area. Use null for viewport. | null |
rootMargin | Margin around the root before intersection is calculated. | "100px 0px" |
threshold | Percentage of target visibility required to trigger callback. | 0, 0.5, 1 |
IntersectionObserverEntry Properties
| Property | Description | Common Use |
isIntersecting | Returns true when the target intersects the root. | Check visible or hidden state. |
intersectionRatio | Percentage of target currently visible. | Track visibility progress. |
target | The observed element. | Add classes or load content. |
boundingClientRect | Target element dimensions and position. | Debug layout visibility. |
intersectionRect | Visible portion of the target. | Calculate visible area. |
rootBounds | Bounds of the root container. | Compare target with viewport or container. |
time | Timestamp when intersection was recorded. | Analytics and timing logic. |
Lazy Load Images Example
<img
class="lazy-image"
data-src="/images/photo.jpg"
alt="Beautiful landscape">
<script>
const images =
document.querySelectorAll(".lazy-image");
const imageObserver =
new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) {
return;
}
const image =
entry.target;
image.src =
image.dataset.src;
image.removeAttribute("data-src");
observer.unobserve(image);
});
});
images.forEach((image) => {
imageObserver.observe(image);
});
</script>
This example loads images only when they are about to become visible. It
improves page speed and reduces unnecessary network requests.
Scroll Animation Example
const cards =
document.querySelectorAll(".animate-card");
const animationObserver =
new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("is-visible");
}
});
},
{
threshold: 0.25
}
);
cards.forEach((card) => {
animationObserver.observe(card);
});
This example adds a class when a card enters the viewport. CSS can then
animate the element with opacity, transform, or transition effects.
Active Section Navigation Example
const sections =
document.querySelectorAll("section[id]");
const navLinks =
document.querySelectorAll("[data-nav-link]");
const sectionObserver =
new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
navLinks.forEach((link) => {
link.classList.toggle(
"active",
link.getAttribute("href") ===
"#" + entry.target.id
);
});
}
});
},
{
threshold: 0.6
}
);
sections.forEach((section) => {
sectionObserver.observe(section);
});
This pattern highlights the active navigation link as users scroll through
long documentation pages or tutorials.
Play Video When Visible
const videos =
document.querySelectorAll("video[data-autoplay]");
const videoObserver =
new IntersectionObserver((entries) => {
entries.forEach((entry) => {
const video =
entry.target;
if (entry.isIntersecting) {
video.play();
} else {
video.pause();
}
});
},
{
threshold: 0.75
}
);
videos.forEach((video) => {
videoObserver.observe(video);
});
Videos can pause when off-screen and resume when visible, improving
performance and reducing unnecessary CPU and battery usage.
Observe Inside Scroll Container
const scrollBox =
document.querySelector("#scrollBox");
const target =
document.querySelector("#item");
const observer =
new IntersectionObserver((entries) => {
entries.forEach((entry) => {
console.log(entry.isIntersecting);
});
},
{
root: scrollBox,
rootMargin: "0px",
threshold: 1
}
);
observer.observe(target);
The root option allows observation inside a scrollable
container instead of the full browser viewport.
Intersection Observer Methods
| Method | Description | Example Syntax |
observe() | Starts observing a target element. | observer.observe(element) |
unobserve() | Stops observing a specific target. | observer.unobserve(element) |
disconnect() | Stops observing all targets. | observer.disconnect() |
takeRecords() | Returns pending observer entries. | observer.takeRecords() |
Common Intersection Observer Use Cases
| Use Case | How It Helps |
| Lazy Loading | Loads images, videos, or components only when needed. |
| Infinite Scroll | Loads more content when the user reaches the bottom. |
| Scroll Animations | Triggers animations when elements enter the viewport. |
| Analytics Tracking | Tracks whether content or ads were actually viewed. |
| Active Navigation | Highlights the current section while scrolling. |
| Video Optimization | Plays or pauses videos based on visibility. |
| Virtual Lists | Detects visible items in long lists. |
| Component Hydration | Loads JavaScript for components only when visible. |
Intersection Observer Best Practices
- Use Intersection Observer instead of heavy scroll event calculations.
- Use
rootMargin to load content before it becomes visible. - Use sensible
threshold values based on the use case. - Call
unobserve() after one-time work such as lazy loading. - Use
disconnect() when observers are no longer needed. - Provide fallback loading for unsupported browsers if required.
- Avoid triggering expensive work too frequently.
- Respect reduced motion preferences for scroll animations.
Common Intersection Observer Mistakes
- Forgetting to call
observe() on target elements. - Using too many observers instead of one shared observer.
- Not calling
unobserve() for one-time lazy loading. - Using large thresholds without understanding visibility ratios.
- Running expensive code every time the callback fires.
- Ignoring browser support in legacy environments.
- Using scroll events when Intersection Observer would be more efficient.
- Animating content without considering accessibility and reduced motion.
Key Takeaways
- The Intersection Observer API detects when elements enter or leave view.
- Use
new IntersectionObserver() to create an observer. - Use
observe() to start watching elements. - Use
entry.isIntersecting and intersectionRatio to detect visibility. - Use
unobserve() and disconnect() to clean up observers. - It is ideal for lazy loading, infinite scrolling, animations, tracking, and active navigation.
Pro Tip
For lazy loading, use a positive rootMargin such as
"200px 0px". This starts loading content shortly before it
appears on screen, creating a smoother user experience.