← Назад к списку
ТехническаяFrontendSenior

Что такое reflow и repaint? Как устроен пайплайн отрисовки и как писать код, не вызывающий лишних перерасчётов?

Короткий ответ

  • Пайплайн кадра: style, layout, paint, composite
  • Reflow (layout) — перерасчёт геометрии, самый дорогой этап
  • Repaint — перерисовка пикселей без изменения геометрии
  • transform и opacity обходятся одной композицией
  • Чтение offsetWidth после записи стилей — принудительный синхронный layout
  • Батчить чтения и записи DOM раздельно
  • Анимации вести через transform, opacity и rAF

Изменения геометрии запускают дорогой reflow, визуальные свойства — repaint, а transform и opacity обрабатываются композитором почти бесплатно; главный анти-паттерн — чередование записи стилей и чтения layout-свойств (layout thrashing).

Как сказать вслух

пример ответа

Когда страница меняется, браузер проходит конвейер: пересчитывает стили, затем геометрию элементов — это называется reflow, затем перерисовывает пиксели — repaint, и в конце склеивает слои на композиторе. Самое дорогое — перерасчёт геометрии, потому что изменение одного элемента может затронуть соседей. Поэтому анимации делают через transform и opacity — они обрабатываются отдельным потоком композитора и не трогают layout. И классическая ошибка — в цикле менять стили и тут же читать размеры: каждое чтение заставляет браузер пересчитать layout синхронно.

Подробный ответ

Основной ответ

Пайплайн кадра: recalc style (какие правила применяются), layout/reflow (размеры и позиции), paint (растеризация в слои), composite (сборка слоёв на GPU). Свойства по стоимости делятся на три группы: геометрические (width, top, margin, шрифты) запускают весь конвейер; визуальные (color, background, box-shadow) — paint и composite; transform и opacity у элемента на собственном слое — только composite, причём композитор работает вне главного потока, и такие анимации не дёргаются даже при занятом JS. Ключевой анти-паттерн — layout thrashing: запись стиля инвалидирует layout, а последующее чтение offsetWidth, getBoundingClientRect или scrollTop заставляет пересчитать его немедленно; в цикле это даёт десятки синхронных reflow. Решение — разделять фазы: сначала все чтения, потом все записи, изменения накапливать в классах или cssText, анимации синхронизировать через requestAnimationFrame. Дополнительно помогают content-visibility, contain и will-change (дозированно: лишние слои едят память).

Ключевые моменты

  • Стоимость свойств. Геометрия — полный конвейер, краска — без layout, transform/opacity — только композиция; это определяет выбор свойств для анимаций.
  • Layout thrashing. Чередование записи стилей и чтения геометрии вызывает принудительные синхронные reflow; чтения и записи батчируют раздельно.
  • Композиторный поток. Анимации transform/opacity продолжают идти плавно, даже когда главный поток занят JS.
  • Инструменты. DevTools Performance показывает фиолетовые блоки layout и предупреждения о forced reflow; там же видно время кадра относительно бюджета ~16 мс.

Практический контекст

Это вопрос для senior-уровня: обычно просят объяснить, почему анимация через top/left дёргается, а через transform — нет, или найти thrashing в сниппете с циклом. В работе знание применяется при анимациях, виртуализации списков, бесконечных лентах и оптимизации INP. Ожидается знакомство с профилировщиком Performance и умение связать это с метриками Core Web Vitals.

Пример кода

// Плохо: чтение после записи в цикле — reflow на каждой итерации
items.forEach(el => {
  el.style.width = base + 'px';
  total += el.offsetHeight; // принудительный layout
});

// Лучше: сначала все чтения, потом все записи
const heights = items.map(el => el.offsetHeight);
items.forEach(el => { el.style.width = base + 'px'; });

Частые ошибки

  • Анимируют width, left и margin вместо transform
  • Не видят forced reflow в коде, где чтение размеров перемешано с записью стилей
  • Вешают will-change на всё подряд, раздувая количество слоёв и память

ИП Кочкин Алексей Сергеевич · ИНН 390509026279 · ОГРНИП 325390000030973 · jiniys2005@yandex.ru