Hey folks, I’m wiring up a scroll + viewport reveal thing for a docs page and I’m trying to keep the main thread free while a worker crunches which sections should be “active” for sticky/parallax bits; the failure mode I keep dodging is janky scroll when DOM reads sneak in.
// main.js
const io = new IntersectionObserver((entries) => {
for (const e of entries) {
worker.postMessage({
id: e.target.dataset.id,
visible: e.isIntersecting,
ratio: e.intersectionRatio,
top: e.boundingClientRect.top,
vh: innerHeight,
});
}
}, { threshold: [0, 0.25, 0.5, 0.75, 1] });
document.querySelectorAll('[data-reveal]').forEach(el => io.observe(el));
const worker = new Worker(new URL('./reveal-worker.js', import.meta.url), { type: 'module' });
worker.onmessage = ({ data }) => {
const el = document.querySelector(`[data-id="${data.id}"]`);
if (!el) return;
el.classList.toggle('is-revealed', data.reveal);
el.style.setProperty('--parallax', data.parallax);
};
// reveal-worker.js
self.onmessage = ({ data }) => {
const reveal = data.visible && data.ratio > 0.2;
const parallax = Math.max(-1, Math.min(1, (data.top / data.vh) - 0.5));
postMessage({ id: data.id, reveal, parallax });
};
Neat part is the concurrency boundary: the worker does the math, the main thread just flips classes/CSS vars, and IntersectionObserver is the only “measurement” I let in.