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
});
OptionTypeDefaultDescription
rootElement | nullnull (viewport)Scrollable ancestor to use as viewport
rootMarginstring'0px'Expand/contract root bounding box (CSS margin syntax)
thresholdnumber | number[]0Fraction(s) of target visibility at which to fire

IntersectionObserverEntry Properties

PropertyTypeDescription
entry.targetElementObserved element
entry.isIntersectingbooleantrue when visible above threshold
entry.intersectionRationumberFraction (0–1) currently visible
entry.intersectionRectDOMRectReadOnlyVisible portion rect
entry.boundingClientRectDOMRectReadOnlyFull target rect
entry.rootBoundsDOMRectReadOnly | nullRoot rect
entry.timenumberDOMHighResTimeStamp 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

PropertyTypeDescription
entry.targetElementObserved element
entry.contentRectDOMRectReadOnlyContent box size (padding excluded)
entry.contentBoxSizeResizeObserverSize[]blockSize, inlineSize
entry.borderBoxSizeResizeObserverSize[]Includes padding + border
entry.devicePixelContentBoxSizeResizeObserverSize[]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 rootMargin on a non-viewport root requires the root to have overflow set.
  • ResizeObserver may 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.