A lightweight scroll reveal with IntersectionObserver and a sticky header offset

Hey everyone, I’m wiring up a docs page and trying to get that “reveal on scroll” feel without paying the parallax tax on performance, since product wants it to stay snappy on low-end phones and not break when the sticky header changes height.

const header = document.querySelector('[data-sticky-header]');
const els = document.querySelectorAll('[data-reveal]');

const getTopOffset = () => (header?.getBoundingClientRect().height ?? 0) + 8;

const io = new IntersectionObserver((entries) => {
  for (const e of entries) {
    if (e.isIntersecting) {
      e.target.classList.add('is-revealed');
      io.unobserve(e.target); // product tradeoff: “once” reveal reduces work
    }
  }
}, {
  root: null,
  rootMargin: () => `-${getTopOffset()}px 0px -10% 0px`,
  threshold: 0.1,
});

els.forEach(el => io.observe(el));

window.addEventListener('resize', () => {
  io.disconnect();
  els.forEach(el => io.observe(el));
}, { passive: true });

Neat part is it stays “viewport aware” even with a sticky header, and the main failure mode I’ve seen is getting the margins wrong and revealing too early/late when the header height changes.