Intersection Observer and Mutation Observer

Difficulty: Intermediate

Question

What is the Intersection Observer API? How does it differ from scroll event listeners?

Answer

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

Code examples

Lazy Loading Images

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.

MutationObserver

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.

Key points

Concepts covered

IntersectionObserver, MutationObserver, Lazy Loading, Infinite Scroll