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

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

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

Причины возникновения reflow и repaint

Reflow вызывают следующие действия:

  • Изменение размеров элемента (width, height, padding, border, margin).
  • Добавление или удаление элементов DOM.
  • Изменение текста в элементе, что может изменить его размеры.
  • Изменение видимости элемента (display: nonedisplay: block).
  • Изменение шрифтов и размеров шрифта.

Repaint возникает при изменении свойств, влияющих только на визуальное отображение, но не на размеры:

  • Цвет текста и фона (color, background-color).
  • Контур элемента (outline).
  • Тени (box-shadow, text-shadow).
  • Непрозрачность (opacity).

Важно понимать, что reflow всегда вызывает repaint, но repaint может происходить без reflow.

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

Библиотека Bliss.js предоставляет удобные методы для работы с DOM, событиями и стилями, что позволяет оптимизировать частоту и масштаб изменений, вызывающих перерисовку страницы.

1. Использование html() и text()

Методы html() и text() в Bliss.js позволяют однократно обновлять содержимое элемента, что минимизирует количество обращений к DOM. Вместо многократных манипуляций с innerHTML:

// Плохо: несколько reflow
document.querySelector('#content').innerHTML = 'Первый блок';
document.querySelector('#content').innerHTML += 'Второй блок';

// Хорошо: один reflow
Bliss.$('#content').html('Первый блокВторой блок');

2. Манипуляции с классами через addClass и removeClass

Изменение стилей через класс предпочтительнее прямого присвоения стилей, так как браузер может оптимизировать перерисовку и применять batch-обновления:

let el = Bliss.$('#box');
el.addClass('active');  // Один reflow для всех стилей
el.removeClass('inactive');

3. Batch-обновления через each и map

Если нужно изменить несколько элементов, лучше использовать методы each или map, чтобы выполнять изменения в цикле внутри одного обхода коллекции:

Bliss.$$('.item').each(el => {
    el.style.width = '200px';
    el.style.backgroundColor = '#f0f0f0';
});

4. Использование DocumentFragment для массовых вставок

При необходимости добавить множество элементов в DOM, создание их внутри DocumentFragment позволяет производить только один reflow при вставке фрагмента:

let fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
    let div = document.createElement('div');
    div.textContent = `Элемент ${i}`;
    fragment.appendChild(div);
}
Bliss.$('#container')[0].appendChild(fragment);

5. Скрытие элементов перед изменениями

При изменении стилей, которые вызывают reflow, полезно временно скрыть элемент (display: none), выполнить изменения, а затем показать. Это предотвращает многократные пересчеты:

let el = Bliss.$('#menu');
el.css('display', 'none');
el.css('width', '300px');
el.css('height', '150px');
el.css('display', 'block');

6. Использование requestAnimationFrame для визуальных обновлений

Частые анимации или динамическое изменение DOM лучше привязывать к requestAnimationFrame, чтобы браузер сам планировал оптимальные моменты перерисовки:

function animateBox() {
    Bliss.$('#box').css('left', '+=5px');
    requestAnimationFrame(animateBox);
}
requestAnimationFrame(animateBox);

7. Оптимизация чтения и записи стилей

Частое чередование чтения и записи стилей (offsetWidth, clientHeight, style.width) приводит к forced reflow. Bliss.js позволяет кэшировать выборки:

let el = Bliss.$('#box');
let width = el[0].offsetWidth;  // Чтение один раз
el.css('width', width + 50 + 'px');  // Запись

Соблюдение этого принципа позволяет избежать лишних перерисовок.

Практические рекомендации

  • Группировать изменения DOM и стилей в одном блоке кода.
  • Использовать классы и CSS для массовых изменений вместо прямого присвоения style.
  • Минимизировать вложенные вызовы чтения и записи свойств layout.
  • Использовать DocumentFragment и методы Bliss.js для пакетной вставки элементов.
  • Привязывать анимации к requestAnimationFrame, а не setTimeout или setInterval.

Эффективное применение этих техник снижает нагрузку на браузер, ускоряет рендеринг страницы и делает интерфейс отзывчивым даже при большом количестве элементов и динамических обновлениях.