Оптимизация Reflow и Repaint: ускоряем веб-страницы – 5 техник
#Веб-разработка #CSS и верстка #Фронтенд CSSДля кого эта статья:
- Веб-разработчики и фронтенд-инженеры
- Специалисты по оптимизации производительности веб-приложений
- Студенты и обучающиеся в области веб-технологий
Когда ваш сайт отзывается на действия пользователя медленнее, чем он моргает, вы теряете деньги. Глубокое понимание механизмов reflow и repaint в браузере — это не просто технический навык, а ваше секретное оружие в борьбе за миллисекунды. Каждый нерациональный reflow стоит вашим пользователям драгоценного времени и терпения. Давайте раскрою 5 проверенных техник, которые буквально заставят ваши страницы летать, а конкурентов — нервно курить в сторонке. 🚀
Что такое Reflow и Repaint: механизмы рендеринга страниц
Прежде чем погружаться в оптимизацию, критически важно понимать, с чем мы имеем дело. Reflow и repaint — это два ресурсоёмких процесса, возникающих при рендеринге веб-страниц, которые часто становятся бутылочным горлышком производительности.
Reflow (перекомпоновка) происходит, когда браузер пересчитывает размеры и положение элементов на странице. Это геометрические вычисления, которые затрагивают весь поток документа или его значительную часть. Repaint (перерисовка) — это процесс обновления визуального отображения элементов без изменения их геометрии.
| Процесс | Описание | Вычислительная сложность | Триггеры |
|---|---|---|---|
| Reflow | Пересчёт размеров и положения элементов | Высокая | width, height, margin, padding, display, position |
| Repaint | Обновление визуального отображения | Средняя | color, background, visibility, outline |
Цепочка рендеринга страницы в браузере выглядит следующим образом:
- Парсинг HTML — создание DOM-дерева
- Парсинг CSS — создание CSSOM-дерева
- Формирование Render Tree — объединение DOM и CSSOM
- Layout (Reflow) — вычисление позиций и размеров
- Paint (Repaint) — отрисовка пикселей
- Compositing — объединение слоёв в финальное изображение
Важно осознавать: каждый раз, когда вы меняете свойства DOM или стили, затрагивающие геометрию, браузеру приходится выполнять reflow всех затронутых элементов и их потомков. После reflow всегда следует repaint, но repaint может происходить и без reflow.
Александр Гришин, Lead Frontend Developer
Я столкнулся с классическим случаем reflow-ада при разработке интерактивной панели мониторинга с более чем 50 динамическими графиками. При каждом обновлении данных (раз в 3 секунды) страница начинала заметно подтормаживать. Расследование показало, что каждое обновление вызывало цепочку reflow-операций, затрагивающих почти все элементы страницы.
Первое, что я сделал — вынес обновление данных в requestAnimationFrame. Затем отследил все места, где происходило чтение геометрических свойств (getBoundingClientRect, offsetWidth и т.д.), и переработал код так, чтобы эти чтения происходили до внесения изменений в DOM. Наконец, применил CSS-свойство will-change для ключевых анимированных элементов.
Результат превзошёл ожидания — время обновления страницы упало с 350мс до 42мс, а страница перестала "заикаться" даже на слабых устройствах.

Минимизация изменений DOM для снижения Reflow-операций
Первая и, вероятно, самая эффективная техника оптимизации — минимизация частоты и масштаба изменений DOM. Каждое изменение в структуре документа потенциально может запустить каскадную реакцию reflow, особенно если затрагиваются элементы, от которых зависит расположение других элементов.
Вот пять стратегий снижения количества reflow-операций при работе с DOM:
- Избегайте повторных манипуляций с DOM — группируйте чтение и запись свойств в отдельные блоки
- Используйте DocumentFragment — собирайте сложные DOM-структуры "за кадром" и добавляйте их одной операцией
- Модифицируйте скрытые элементы — используйте display: none перед внесением множественных изменений
- Кэшируйте геометрические свойства — сохраняйте результаты getBoundingClientRect() в переменных
- Изменяйте классы на самом нижнем уровне DOM — модификации родительских элементов затрагивают всех потомков
Рассмотрим пример неоптимизированного и оптимизированного подходов к добавлению элементов:
❌ Неоптимально:
// Вызывает reflow на каждой итерации
for(let i = 0; i < 100; i++) {
document.body.appendChild(document.createElement('div'));
}
✅ Оптимально:
// Вызывает reflow только один раз
const fragment = document.createDocumentFragment();
for(let i = 0; i < 100; i++) {
fragment.appendChild(document.createElement('div'));
}
document.body.appendChild(fragment);
Применяя эту технику к реальным проектам, я часто вижу ускорение рендеринга в 3-5 раз, особенно при работе со списками и таблицами с большим количеством данных.
Оптимизация CSS-анимаций: transform и opacity вместо reflow
CSS-анимации и переходы — часто становятся источником серьезных проблем с производительностью, особенно если они затрагивают свойства, вызывающие reflow. Ключ к плавным анимациям — использование свойств, которые браузеры могут оптимизировать.
Существует две категории CSS-свойств с точки зрения оптимизации:
- Вызывающие reflow: width, height, margin, padding, top, left, right, bottom, font-size и т.д.
- Вызывающие только repaint: transform, opacity, filter, visibility, background-color и т.д.
| Свойство | Процесс | Производительность | Использование GPU |
|---|---|---|---|
| width/height | Reflow + Repaint | Низкая | Нет |
| top/left с position: absolute | Reflow + Repaint | Средняя | Нет |
| transform: translate() | Композиция | Высокая | Да |
| opacity | Композиция | Высокая | Да |
Вот оптимальные замены для популярных анимационных эффектов:
- ❌ left/top → ✅ transform: translate(x, y)
- ❌ width/height → ✅ transform: scale(x, y)
- ❌ display: none/block → ✅ opacity + visibility (для переходов)
Пример оптимизированной анимации движения:
/* Неоптимальная анимация с reflow */
@keyframes move-bad {
0% { left: 0; top: 0; }
100% { left: 100px; top: 100px; }
}
/* Оптимизированная анимация без reflow */
@keyframes move-good {
0% { transform: translate(0, 0); }
100% { transform: translate(100px, 100px); }
}
Для максимальной производительности анимаций также рекомендуется использовать свойство will-change, которое сообщает браузеру о намерении анимировать элемент:
.animated-element {
will-change: transform, opacity;
/* Другие стили */
}
Однако с will-change нужно быть осторожным — применение этого свойства к слишком большому количеству элементов может привести к обратному эффекту и ухудшить производительность. 🔍
Михаил Соколов, Senior UI Developer
В прошлом году я работал над обновлением интерфейса крупного e-commerce проекта, где ключевой проблемой стала анимация корзины покупок. При добавлении товара небольшая иконка "летела" от кнопки в корзину с приятным визуальным эффектом.
На первой итерации я использовал jQuery с анимацией позиционирования через left/top свойства. На мощных машинах всё работало хорошо, но тестирование на бюджетных смартфонах показало ужасные результаты — FPS падал до 10-15, а вся страница начинала тормозить.
Переписав анимацию на чистый CSS с transform: translate3d() и добавив аппаратное ускорение, я добился стабильных 60 FPS даже на слабых устройствах. Важный нюанс: пришлось также добавить z-index для корректного отображения слоёв и предварительно вычислять конечные координаты с помощью JavaScript перед запуском анимации.
Клиент был настолько впечатлён результатом, что мы полностью переписали все анимации в проекте по тому же принципу, что привело к общему повышению производительности сайта на 35% по метрикам Google PageSpeed.
Пакетная обработка DOM-операций для сокращения repaint
Когда необходимы множественные изменения DOM, ключевым фактором оптимизации становится группировка (батчинг) операций. Это позволяет браузеру оптимизировать процессы reflow и repaint, выполняя их реже и эффективнее.
Существует несколько техник пакетной обработки DOM-операций:
- Offline DOM-манипуляции — временное отключение элемента от живого DOM
- Использование requestAnimationFrame — синхронизация изменений с циклом перерисовки браузера
- Виртуальный DOM — применение изменений пакетами (как в React, Vue)
- CSS-батчинг — группировка изменений стилей через классы
Рассмотрим реализацию offline DOM-манипуляций:
// Плохой подход — многократные reflow
function updateListItems(items) {
const list = document.getElementById('my-list');
// Каждая операция вызывает reflow
for(let i = 0; i < items.length; i++) {
const li = document.createElement('li');
li.textContent = items[i];
list.appendChild(li); // reflow происходит здесь
}
}
// Хороший подход — один reflow
function updateListItemsOptimized(items) {
const list = document.getElementById('my-list');
// 1. Убираем элемент из потока документа
const oldDisplay = list.style.display;
list.style.display = 'none';
// 2. Выполняем манипуляции
for(let i = 0; i < items.length; i++) {
const li = document.createElement('li');
li.textContent = items[i];
list.appendChild(li); // reflow не происходит, т.к. элемент не в потоке
}
// 3. Возвращаем элемент в поток (только один reflow)
list.style.display = oldDisplay;
}
Еще более эффективный метод — использование requestAnimationFrame для синхронизации изменений DOM с циклом отрисовки браузера:
function animateElements() {
const elements = document.querySelectorAll('.animated');
// Группируем чтение метрик DOM
const metrics = Array.from(elements).map(el => {
return {
element: el,
currentLeft: parseFloat(getComputedStyle(el).left),
targetLeft: parseFloat(el.dataset.targetLeft)
};
});
// Выполняем запись в DOM в момент перерисовки
requestAnimationFrame(() => {
metrics.forEach(item => {
const newLeft = item.currentLeft + (item.targetLeft – item.currentLeft) * 0.1;
item.element.style.left = `${newLeft}px`;
});
if (stillAnimating) {
animateElements(); // Продолжаем анимацию
}
});
}
При работе с большими списками или таблицами также полезно использовать технику "виртуального скроллинга", при которой рендерятся только видимые элементы, а не весь список целиком. Библиотеки вроде react-virtualized, react-window или vue-virtual-scroller могут значительно повысить производительность рендеринга длинных списков. 📱
Инструменты диагностики и измерения reflow-производительности
Без точных измерений любая оптимизация превращается в гадание. К счастью, современные браузеры предоставляют мощные инструменты для диагностики и профилирования производительности рендеринга.
Ключевые инструменты для измерения и отладки reflow и repaint проблем:
- Chrome DevTools Performance panel — подробное профилирование рендеринга
- Paint Flashing — визуализация областей repaint в реальном времени
- Layers panel — анализ композитных слоёв
- FPS meter — отслеживание частоты кадров
- User Timing API — ручные замеры производительности в JavaScript
Для активации визуализации repaint в Chrome перейдите в DevTools > ⋮ > More tools > Rendering и включите "Paint flashing". Все области, которые проходят через процесс перерисовки, будут подсвечиваться зеленым.
Для профилирования сложных проблем с производительностью используйте вкладку Performance:
- Откройте DevTools (F12) и перейдите на вкладку Performance
- Нажмите кнопку "Record" и выполните действия, которые вызывают проблемы
- Остановите запись и проанализируйте временную шкалу
- Обратите внимание на красные полосы (долгие task) и события "Recalculate Style", "Layout" (reflow) и "Paint"
Самыми информативными метриками при анализе reflow/repaint являются:
- FPS — частота кадров (целевое значение — 60 FPS)
- CPU usage — нагрузка на процессор
- Layout events — события reflow
- Paint events — события repaint
Для точного измерения производительности в коде используйте Performance API:
// Измерение времени операции с DOM
performance.mark('start-dom-operations');
// Выполняем операции с DOM
// ...
performance.mark('end-dom-operations');
performance.measure('DOM Operations', 'start-dom-operations', 'end-dom-operations');
// Анализ результатов
const measures = performance.getEntriesByType('measure');
console.table(measures);
// Очистка маркеров
performance.clearMarks();
performance.clearMeasures();
Также существуют специализированные инструменты и плагины для отслеживания reflow-проблем:
| Инструмент | Тип | Основное назначение |
|---|---|---|
| Browser-Perf | Node.js утилита | Автоматизированное измерение производительности |
| Web Vitals | JavaScript библиотека | Измерение Core Web Vitals |
| CSS Triggers | Веб-справочник | Таблица CSS свойств, вызывающих reflow/repaint |
| WebPageTest | Онлайн сервис | Комплексный анализ производительности |
При профилировании всегда тестируйте на реальных устройствах, а не только в эмуляторах — разница в производительности может быть колоссальной, особенно на мобильных устройствах среднего класса. 📊
Эффективная оптимизация reflow и repaint — это не разовое мероприятие, а образ мышления. Применяя описанные техники, вы можете значительно повысить производительность ваших веб-приложений. Помните, что пользователи редко отмечают хорошую производительность, но никогда не забывают плохую. Настоящее мастерство заключается в балансе между визуальной привлекательностью и скоростью работы. Ваша задача как разработчика — сделать этот компромисс незаметным для пользователя.
Владимир Лисицын
разработчик фронтенда