Difficulty: Intermediate
What is the Intersection Observer API? How does it differ from scroll event listeners?
The Intersection Observer API asynchronously observes when a target element enters or exits a viewport. It is far more performant than scroll event listeners because:
- It runs off the main thread (no layout thrashing) - The browser batches observations - No manual scroll position calculations needed
Mutation Observer observes DOM changes (child additions, attribute changes, text modifications).
Use cases: - IntersectionObserver: lazy image loading, infinite scroll, animating elements on scroll, ad impression tracking - MutationObserver: observing third-party DOM changes, auto-save on content edits
const imageObserver = new IntersectionObserver(
(entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
observer.unobserve(img);
}
});
},
{
rootMargin: '100px 0px',
threshold: 0
}
);
document.querySelectorAll('img[data-src]').forEach(img => {
imageObserver.observe(img);
});
// Infinite scroll
const loadMoreObserver = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
loadNextPage();
}
});
loadMoreObserver.observe(document.querySelector('#sentinel'));
rootMargin pre-loads images before they enter view. observer.unobserve() stops watching once the image is loaded.
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
console.log('Children added:', mutation.addedNodes.length);
}
if (mutation.type === 'attributes') {
console.log(`Attribute '${mutation.attributeName}' changed`);
}
});
});
observer.observe(document.querySelector('#content'), {
childList: true,
attributes: true,
subtree: true
});
// Always disconnect to prevent memory leaks
observer.disconnect();
// Detect when a third-party widget loads
const chatObserver = new MutationObserver((mutations, obs) => {
const chat = document.querySelector('.chat-widget');
if (chat) {
initListeners(chat);
obs.disconnect();
}
});
chatObserver.observe(document.body, { childList: true, subtree: true });
MutationObserver receives batched MutationRecord objects. disconnect() must be called to stop observation and avoid memory leaks.
IntersectionObserver, MutationObserver, Lazy Loading, Infinite Scroll