Hey folks, I’m wiring up a “sticky summary” panel and I’m trying to keep it aligned with a content column, but I’m seeing random scroll hitching and occasional 1px jumps when the page is busy (images loading, fonts swapping).
const el = document.querySelector('.summary');
function sync() {
// seems to trigger layout + style recalcs at bad times
const { width, left } = document
.querySelector('.content')
.getBoundingClientRect();
el.style.width = `${width}px`;
el.style.transform = `translateX(${left}px)`;
}
window.addEventListener('scroll', sync, { passive: true });
window.addEventListener('resize', sync);
new ResizeObserver(sync).observe(document.querySelector('.content'));
What’s the least risky pattern to keep this visually stable (and avoid forced reflow) when scroll/resize/RO callbacks can interleave and the layout is still settling?
getBoundingClientRect() in the scroll callback is the classic “please do layout right now” button, so yeah, it can hitch when images/fonts are still changing things.
I’d keep your exact idea (coalesce into one requestAnimationFrame), but make it strictly “read first, then write” in that one frame, and round the translate to avoid the fractional-pixel shimmer:
const content = document.querySelector('.content');
const summary = document.querySelector('.summary');
let rafId = 0;
function scheduleSync() {
if (rafId) return;
rafId = requestAnimationFrame(() => {
rafId = 0;
// read
const { width, left } = content.getBoundingClientRect();
// write
summary.style.width = `${width}px`;
summary.style.transform = `translateX(${Math.round(left)}px)`;
});
}
addEventListener('scroll', scheduleSync, { passive: true });
addEventListener('resize', scheduleSync);
new ResizeObserver(scheduleSync).observe(content);
The rounding is the part that usually fixes the “1px jump” for me when the page is landing on half pixels during font swap. I’m not 100% sure that’s your exact source, but it’s a very common culprit.