IntersectionObserver replaces the scroll listener

Detecting whether an element is on screen has meant a scroll handler calling getBoundingClientRect(), which forces a synchronous layout on every scroll event and is the classic cause of janky infinite scroll.

const io = new IntersectionObserver((entries) => {
  entries.filter(e => e.isIntersecting).forEach(e => {
    load(e.target);
    io.unobserve(e.target);
  });
}, { rootMargin: '200px' });

document.querySelectorAll('[data-src]').forEach(el => io.observe(el));

The work happens off the main thread and the callback only fires when something crosses the threshold, so an idle page costs nothing. rootMargin is what makes it useful for lazy loading — starting the fetch 200px before the image is visible means it has usually arrived by the time it is. Chrome 51 has it and nothing else does yet, so a polyfill or a scroll fallback is still required.