IntersectionObserver API
debt(d7/e3/b3/t7)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints indicate no automated detection ('automated: no') and only loose code patterns (getBoundingClientRect|scroll.*img). Missing unobserve() calls, threshold misconfiguration, and missing rootMargin are runtime/behavioral issues that won't be caught by linters or type checkers — they require manual testing or code review to surface.
Closest to 'simple parameterised fix' (e3). The quick_fix describes a straightforward pattern replacement: swap scroll event + getBoundingClientRect for IntersectionObserver, add rootMargin configuration, and ensure unobserve() calls. This is contained within a single component (lazy-loading or scroll handler) and doesn't require cross-file refactoring.
Closest to 'localised tax' (b3). IntersectionObserver is scoped to web contexts and typically isolated to specific performance-critical components (lazy loading, infinite scroll, visibility tracking). It doesn't impose a persistent tax across the codebase; once a component uses it correctly, the choice is localized. It doesn't reshape the overall system architecture.
Closest to 'serious trap' (t7). The misconception field explicitly states a major gotcha: developers expect IntersectionObserver to fire synchronously during scroll (like scroll events), but it actually fires asynchronously between frames, batching intersections. This contradicts familiar scroll-event behavior. Additionally, common_mistakes reveal threshold:0 vs threshold:1 confusion and the subtle need for rootMargin preloading—both non-obvious behaviors that trip developers.
TL;DR
Explanation
IntersectionObserver(callback, options) observes elements for viewport intersection. Options: root (default viewport), rootMargin (expand/shrink root), threshold (0–1, fraction visible to trigger). The callback receives IntersectionObserverEntry[] with isIntersecting, intersectionRatio, boundingClientRect. Use cases: lazy load images (observe img, set src when visible), infinite scroll, animate elements on scroll, sticky header detection. Unobserve after first intersection for one-shot triggers. Far more performant than scroll event + getBoundingClientRect polling.
Common Misconception
Why It Matters
Common Mistakes
- Not calling unobserve() after one-shot events like lazy loading.
- Using threshold:0 for 'element is fully visible' — that's threshold:1.
- Not setting rootMargin to preload images slightly before they're visible.
Code Examples
window.addEventListener('scroll', () => {
images.forEach(img => {
const rect = img.getBoundingClientRect();
if (rect.top < window.innerHeight) img.src = img.dataset.src;
});
});
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.src = entry.target.dataset.src;
obs.unobserve(entry.target); // One-shot
}
});
}, { rootMargin: '200px' }); // Preload 200px before visible
document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));