Layout thrashing: многократные пересчёты макета в одном кадре и как их убрать
Что происходит
Браузер старается пересчитывать макет один раз за кадр. Но если код пишет стиль, потом читает геометрию (offsetWidth, getBoundingClientRect), потом снова пишет — в цикле — каждое чтение принудительно запускает свежий синхронный пересчёт макета всей страницы. Цикл по 100 элементам может вызвать 100 полных reflow в одном кадре.
Стоимость пропорциональна размеру DOM: код, безобидный на маленькой странице, убивает INP на странице с тысячами узлов.
Как распознать
- Presentation delay (работа рендеринга после обработчика) доминирует в разбивке INP.
- В DevTools Performance внутри одного кадра видно много фиолетовых блоков Layout, часто с пометкой «Forced reflow».
- Код чередует чтения (offsetTop, clientHeight, getComputedStyle) и записи (style, classList) внутри цикла.
Как починить
- Сгруппируйте сначала все чтения, потом все записи — никогда не чередуйте их в цикле.
- Перенесите записи в requestAnimationFrame, чтобы они попадали прямо перед следующей отрисовкой.
- Предпочитайте CSS-решения (классы, transform, flex/grid) измерению и позиционированию элементов в JS.
Проблема
items.forEach((el) => {
el.style.width = base + "px" // write
const h = el.offsetHeight // read → forced reflow, every iteration
el.style.height = h * 2 + "px" // write
})
Фикс
const heights = items.map((el) => el.offsetHeight) // all reads
requestAnimationFrame(() => {
items.forEach((el, i) => { // all writes
el.style.width = base + "px"
el.style.height = heights[i] * 2 + "px"
})
})
Просканировать свой сайт бесплатно →