Минимизация reflow и repaint

Reflow (или layout) — это процесс вычисления браузером размеров, положения и геометрии элементов на странице после изменений в DOM или стилях. Любое изменение, которое влияет на размеры, расположение или поток элементов, может вызвать reflow. Примеры изменений, вызывающих reflow:

  • Добавление, удаление или изменение элементов DOM.
  • Изменение размеров, отступов, полей, рамок и шрифтов.
  • Изменение видимости элемента через display: none/block.
  • Изменение содержимого текста, которое влияет на размеры блока.

Repaint (или paint) — процесс перерисовки элементов на экране без перерасчета геометрии. Обычно repaint вызывается, когда меняется визуальный стиль элемента, но не его размер или положение. Примеры:

  • Изменение цвета текста или фона (color, background-color).
  • Изменение тени (box-shadow).
  • Изменение прозрачности (opacity).

Взаимосвязь reflow и repaint: Reflow всегда сопровождается repaint, потому что изменение размеров или позиции элементов требует их перерисовки. Repaint может выполняться без reflow, если затронуты только визуальные свойства.


Влияние на производительность

Частые и тяжелые reflow и repaint сильно замедляют работу интерфейса, особенно на сложных страницах с большим количеством элементов. Каждый reflow — это синхронная операция, блокирующая рендеринг, что ведет к лагам и снижению отзывчивости страницы.

Факторы, увеличивающие нагрузку:

  • Изменение DOM внутри циклов или часто вызываемых функций.
  • Одновременное изменение нескольких стилей, заставляющее браузер повторно вычислять layout.
  • Анимации, которые влияют на layout вместо GPU-ускоренных свойств.

Методы минимизации reflow

  1. Группировка изменений DOM и стилей

    • Вместо поэтапного изменения элементов лучше собирать все изменения и применять их за один раз.
    • Использование DocumentFragment для добавления множества элементов в DOM перед вставкой в основной документ.
  2. Избегание изменения layout-зависимых свойств

    • Свойства типа offsetWidth, clientHeight, scrollTop заставляют браузер выполнять forced reflow.
    • Если требуется вычислить размеры, лучше сохранять результат в переменной и использовать повторно, вместо многократных обращений к DOM.
  3. Использование классов CSS

    • Вместо изменения множества inline-стилей по отдельности, менять один класс с предопределёнными стилями.

    • Пример:

      // Плохо
      element.style.width = "100px";
      element.style.height = "50px";
      
      // Хорошо
      element.classList.add("active");
    • Изменение класса вызывает один reflow и repaint вместо нескольких.

  4. Использование visibility вместо display

    • display: none удаляет элемент из потока и вызывает reflow, тогда как visibility: hidden скрывает элемент без изменения layout.
  5. Использование position: absolute или fixed для анимаций

    • Элементы с абсолютным или фиксированным позиционированием не влияют на поток и не вызывают reflow соседних элементов при изменении позиции.

Методы минимизации repaint

  1. Изменение свойств, оптимизированных для GPU

    • transform и opacity изменяются с помощью GPU и вызывают только repaint или отдельный слой рендеринга.
    • Анимации типа left или top вызывают reflow, а transform: translateX() — только repaint.
  2. Комбинирование стилей

    • Изменение нескольких визуальных свойств за один вызов element.style.cssText или через класс.
    • Это предотвращает множественные repaint для каждого свойства отдельно.
  3. Использование will-change

    • Свойство CSS will-change: transform, opacity; сигнализирует браузеру о планируемых изменениях и позволяет заранее создать отдельный слой для GPU-рендеринга.

Инструменты анализа

  • DevTools Chrome

    • Панель Performance позволяет увидеть, сколько времени тратится на reflow и repaint.
    • Вкладка Rendering может отображать красным элементы, вызывающие repaint.
  • Метрики

    • Layout Thrashing — когда частые reflow выполняются подряд в цикле.
    • Forced Reflow — вызовы DOM-свойств, которые принудительно вычисляют layout.

Пример практического применения

Для библиотек типа Driver.js, которые создают интерактивные подсказки и шаги по интерфейсу:

  • Использовать DocumentFragment для подготовки подсказок перед добавлением на страницу.
  • Изменять позиции подсказок через transform: translate() вместо изменения top и left.
  • Применять классы для показа/скрытия шагов, чтобы минимизировать количество reflow.
  • Избегать многократных запросов offsetWidth и getBoundingClientRect при анимации.
// Эффективное обновление позиции подсказки
const tooltip = document.querySelector(".tooltip");
tooltip.style.transform = `translate(${x}px, ${y}px)`;
tooltip.classList.add("visible");

Такой подход позволяет Driver.js работать плавно, не вызывая лишних перерисовок и перерасчетов layout на странице.


Рекомендации для оптимизации на практике

  • Предварительное вычисление размеров и позиций, если элементы будут часто перемещаться.
  • Минимизация прямых манипуляций DOM внутри анимационных циклов.
  • Использование CSS-анимаций и transition с GPU-ускоренными свойствами.
  • Группировка изменений в одну операцию для снижения количества reflow и repaint.

Эти методы обеспечивают более плавный интерфейс, сокращают нагрузку на основной поток браузера и увеличивают отзывчивость веб-приложений.