Intersection Observer API
debt(d5/e3/b3/t7)
Closest to 'specialist tool catches it' (d5). The detection_hints list Lighthouse and ESLint, and the code_pattern indicates ESLint can flag scroll event listeners used for visibility detection. This is not a compiler/syntax error (d1) nor caught by a default linter rule out of the box (d3), but a specialist performance audit tool like Lighthouse or a configured ESLint rule can surface it.
Closest to 'simple parameterised fix' (e3). The quick_fix describes replacing scroll event listeners with IntersectionObserver — a targeted refactor within the component(s) using scroll-based visibility. It is more than a one-line patch (e1) because the callback logic and observer setup must be restructured, but it is contained within one component at a time rather than spanning multiple files.
Closest to 'localised tax' (b3). The applies_to scope is web contexts generally, and the tags are frontend/javascript/performance. Misuse (scroll listeners for visibility) imposes a performance tax on the affected feature/component but doesn't structurally poison the broader codebase — other components are unaffected unless they also use scroll-based detection.
Closest to 'serious trap' (t7). The misconception field states developers believe scroll event listeners are fine for visibility detection, which contradicts the actual behavior (hundreds of synchronous main-thread firings vs. asynchronous off-main-thread callbacks). A competent developer familiar with DOM events would naturally reach for scroll listeners, making this a serious behavioral mismatch. The common_mistakes also reveal multiple subtle configuration traps (threshold, rootMargin, not disconnecting) that compound the cognitive load.
Also Known As
TL;DR
Explanation
The Intersection Observer API calls a callback when a target element intersects with a root element (or the viewport). It is asynchronous and off the main thread — far more efficient than scroll event listeners which fire on every pixel of scroll. Use cases: lazy loading images, infinite scroll, triggering CSS animations when elements enter view, sticky header detection, and ad impression tracking. threshold (0.0-1.0) defines how much of the element must be visible to trigger, rootMargin pre-loads before the element is fully visible.
Common Misconception
Why It Matters
Common Mistakes
- Not disconnecting the observer after the element is loaded — the observer keeps running unnecessarily.
- threshold: 1.0 for lazy loading — waits until the element is fully visible; use 0.1 to preload before it appears.
- No rootMargin for lazy loading — images load exactly as they enter the viewport; add '200px' margin to preload ahead.
- Using Intersection Observer for precise scroll position — it only fires at intersection thresholds, not on every pixel.
Code Examples
// Scroll event — main thread, fires hundreds of times per second:
window.addEventListener('scroll', () => {
document.querySelectorAll('img[data-src]').forEach(img => {
const rect = img.getBoundingClientRect();
if (rect.top < window.innerHeight) {
img.src = img.dataset.src; // Expensive DOM query on every scroll event
}
});
});
// Intersection Observer — async, efficient:
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img); // Stop observing once loaded
}
});
}, { rootMargin: '200px', threshold: 0.1 }); // Preload 200px before visible
document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));