Basic Usage
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log('Element is visible:', entry.target);
}
});
});
// Watch an element
const el = document.getElementById('my-element');
observer.observe(el);
// Stop watching
observer.unobserve(el);
observer.disconnect(); // stop all
Scroll Animations
/* CSS */
.fade-in {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.5s ease, transform 0.5s ease;
}
.fade-in.visible {
opacity: 1;
transform: translateY(0);
}
// JS — observe all .fade-in elements
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
observer.unobserve(entry.target); // animate only once
}
});
}, { threshold: 0.15 }); // trigger when 15% visible
document.querySelectorAll('.fade-in').forEach(el => observer.observe(el));
Lazy Loading Images
<!-- HTML: use data-src instead of src -->
<img data-src="/images/photo.jpg" alt="Photo" class="lazy"/>
const imageObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
imageObserver.unobserve(img);
}
});
}, { rootMargin: '200px' }); // start loading 200px before visible
document.querySelectorAll('img.lazy').forEach(img => imageObserver.observe(img));
💡 Note: Modern browsers support the native
loading="lazy" attribute on images, which is even simpler. Use IntersectionObserver when you need custom control over the loading behaviour.Options Explained
const observer = new IntersectionObserver(callback, {
root: null, // null = viewport; or a scrollable element
rootMargin: '0px', // expand/shrink root box (like CSS margin)
threshold: 0.5 // 0=any pixel visible, 1=fully visible, 0.5=half visible
// Array: [0, 0.25, 0.5, 0.75, 1] fires at each point
});