Web APIs Cheatsheet
Intersection and Resize Observers
Use this Web APIs reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
IntersectionObserver — Basic Usage
const observer = new IntersectionObserver(entries => { for (const entry of entries) { if (entry.isIntersecting) { console.log(entry.target, 'is visible'); } } }, options); observer.observe(element); observer.unobserve(element); observer.disconnect(); // stop observing all targets const records = observer.takeRecords(); // flush pending entries
Constructor Options
const observer = new IntersectionObserver(callback, { root: null, // null = viewport; or any scrolling ancestor element rootMargin: '0px', // CSS margin syntax: '100px 0px' expands root box threshold: 0, // 0–1 or array: fire when this fraction is visible // threshold: [0, 0.25, 0.5, 0.75, 1] — fire at each 25% step });
| Option | Type | Default | Description |
|---|---|---|---|
root | Element | null | null (viewport) | Scrollable ancestor to use as viewport |
rootMargin | string | '0px' | Expand/contract root bounding box (CSS margin syntax) |
threshold | number | number[] | 0 | Fraction(s) of target visibility at which to fire |
IntersectionObserverEntry Properties
| Property | Type | Description |
|---|---|---|
entry.target | Element | Observed element |
entry.isIntersecting | boolean | true when visible above threshold |
entry.intersectionRatio | number | Fraction (0–1) currently visible |
entry.intersectionRect | DOMRectReadOnly | Visible portion rect |
entry.boundingClientRect | DOMRectReadOnly | Full target rect |
entry.rootBounds | DOMRectReadOnly | null | Root rect |
entry.time | number | DOMHighResTimeStamp of observation |
Lazy Loading Images
const imageObserver = new IntersectionObserver((entries, obs) => { for (const entry of entries) { if (!entry.isIntersecting) continue; const img = entry.target; img.src = img.dataset.src; img.removeAttribute('data-src'); obs.unobserve(img); // stop after loading } }, { rootMargin: '200px' }); // load 200px before entering viewport document.querySelectorAll('img[data-src]').forEach(img => imageObserver.observe(img));
Infinite Scroll
const sentinel = document.getElementById('sentinel'); // empty div at bottom of list const scrollObserver = new IntersectionObserver(([entry]) => { if (entry.isIntersecting) loadMore(); }, { threshold: 0.1 }); scrollObserver.observe(sentinel);
Animate on Scroll
const animObserver = new IntersectionObserver(entries => { for (const entry of entries) { entry.target.classList.toggle('visible', entry.isIntersecting); } }, { threshold: 0.2 }); document.querySelectorAll('.animate').forEach(el => animObserver.observe(el));
Sticky Header Detection
// Add a sentinel above the sticky header; when it exits viewport, header is sticky const sentinel = document.createElement('div'); document.querySelector('header').before(sentinel); new IntersectionObserver(([entry]) => { header.classList.toggle('is-sticky', !entry.isIntersecting); }).observe(sentinel);
Track Percentage Visible
new IntersectionObserver(([entry]) => { console.log(`${Math.round(entry.intersectionRatio * 100)}% visible`); }, { threshold: Array.from({ length: 101 }, (_, i) => i / 100) }).observe(el);
ResizeObserver — Basic Usage
const resizeObserver = new ResizeObserver(entries => { for (const entry of entries) { const { contentRect, target } = entry; console.log(target, contentRect.width, contentRect.height); } }); resizeObserver.observe(element); resizeObserver.observe(element, { box: 'border-box' }); // or 'content-box', 'device-pixel-content-box' resizeObserver.unobserve(element); resizeObserver.disconnect();
ResizeObserverEntry Properties
| Property | Type | Description |
|---|---|---|
entry.target | Element | Observed element |
entry.contentRect | DOMRectReadOnly | Content box size (padding excluded) |
entry.contentBoxSize | ResizeObserverSize[] | blockSize, inlineSize |
entry.borderBoxSize | ResizeObserverSize[] | Includes padding + border |
entry.devicePixelContentBoxSize | ResizeObserverSize[] | Physical pixel dimensions |
// ResizeObserverSize entry.contentBoxSize[0].inlineSize; // logical width entry.contentBoxSize[0].blockSize; // logical height
Responsive Canvas with ResizeObserver
const canvas = document.getElementById('c'); const ctx = canvas.getContext('2d'); new ResizeObserver(([entry]) => { const dpr = window.devicePixelRatio || 1; const { inlineSize: w, blockSize: h } = entry.contentBoxSize[0]; canvas.width = Math.round(w * dpr); canvas.height = Math.round(h * dpr); ctx.scale(dpr, dpr); draw(); }).observe(canvas);
Component Size Breakpoints
// Element queries — react to element's own size, not the window new ResizeObserver(([{ contentRect }]) => { const { width } = contentRect; el.classList.toggle('compact', width < 400); el.classList.toggle('wide', width >= 800); }).observe(el);
Gotchas
- IntersectionObserver callbacks run asynchronously after layout and paint — safe to read layout, but not guaranteed to be synchronous with user action.
- Setting
rootMarginon a non-viewport root requires the root to haveoverflowset.ResizeObservermay fire synchronously during layout, causing loop notifications — browsers have a guard; avoid reading layout in the callback and immediately resizing back to the same value.- Both observers batch entries — a single callback invocation can contain entries for multiple elements.
disconnect()removes all observed elements;unobserve(el)removes one.