Layout Thrashing

Материал из Энциклопедия интернет-маркетинга MarketWiki

Layout Thrashing (растасовка макета, синхронное принудительное перекомпоновывание) - это паттерн неэффективного взаимодействия с DOM в веб-разработке, при котором JavaScript многократно, в быстрой последовательности, вызывает принудительную перекомпоновку (forced reflow) страницы, создавая цикл «изменение → чтение → изменение → чтение». В результате браузер многократно выполняет дорогостоящие операции расчёта геометрии элементов, что приводит к резкому падению производительности, зависаниям интерфейса и ухудшению показателей Core Web Vitals.

Для современных маркетологов понимание проблемы layout thrashing важно, поскольку она напрямую влияет на пользовательский опыт на сайте. Медленный, «тормозящий» интерфейс увеличивает показатель отказов, снижает конверсию и ухудшает позиции в поисковой выдаче (а особенно страдают метрики INP - Interaction to Next Paint).

Коротко: Layout thrashing - это когда код на сайте заставляет браузер постоянно пересчитывать расположение элементов, вместо того чтобы сделать это один раз. В результате страница «тормозит», дёргается, а показатели Core Web Vitals падают.

Определение и сущность

[править]

Чтобы понять layout thrashing, нужно разобраться в том, как браузер отображает веб-страницу. Когда браузер загружает HTML, CSS и JavaScript, он выполняет серию операций:

1. Парсинг HTML и CSS: Построение DOM-дерева и CSSOM-дерева.

2. Компоновка (Layout/Reflow): Расчёт геометрических параметров каждого элемента (размеры, позиции).

3. Отрисовка (Paint): Заливка пикселей.

4. Композиция (Composite): Объединение слоёв в финальное изображение.

Обычно браузер выполняет эти операции асинхронно, группируя изменения и применяя их за один проход. Однако когда JavaScript-код считывает геометрические свойства элемента сразу после того, как изменил его стили или структуру DOM, браузер вынужден немедленно выполнить компоновку, чтобы предоставить актуальные данные. Это называется принудительной перекомпоновкой (forced reflow).

Layout thrashing возникает, когда такие принудительные перекомпоновки происходят многократно в быстрой последовательности, особенно внутри циклов. Каждая итерация цикла вызывает очередную перекомпоновку, что создаёт «эффект встряски» - браузер тратит всё время на расчёты, а не на отображение контента.

Как возникает layout thrashing

[править]

Классический пример layout thrashing - код, который в цикле поочерёдно изменяет стили элементов и тут же считывает их геометрические параметры:

// Плохой код - вызывает layout thrashing
const boxes = document.querySelectorAll('.box');

for (let i = 0; i < boxes.length; i++) {
  // Запись: изменяем стиль
  boxes[i].style.width = `${i * 50}px`;
  // Чтение: немедленно запрашиваем геометрию
  // Браузер вынужден выполнить layout для этого элемента
  const height = boxes[i].offsetHeight;
  console.log(`Высота: ${height}`);
}

В этом примере каждая итерация цикла вызывает принудительную перекомпоновку. Если элементов много (например, 100), браузер выполнит layout 100 раз подряд, вместо того чтобы сделать это один раз после завершения всех изменений.

Другой пример - UI-компоненты, такие как слайдеры или карусели:

  • Итерация по списку изображений.
  • Добавление слайда для каждого изображения.
  • Измерение размера добавленного слайда.
  • Применение дополнительных стилей на основе измеренных размеров.

Этот паттерн вызывает layout thrashing, потому что код постоянно чередует запись (добавление элементов) и чтение (измерение размеров).

Свойства и методы, вызывающие принудительную перекомпоновку

[править]

Некоторые свойства и методы JavaScript требуют актуальных геометрических данных и поэтому могут вызвать принудительную перекомпоновку.

Свойства и методы, вызывающие forced reflow
Категория Свойства / Методы
Размеры элемента offsetHeight, offsetWidth, offsetTop, offsetLeft
Клиентские размеры clientHeight, clientWidth, clientTop, clientLeft
Скролл scrollHeight, scrollWidth, scrollTop, scrollLeft
Прямоугольник getBoundingClientRect(), getClientRects()
Окно window.innerHeight, window.innerWidth, window.scrollY
Стили getComputedStyle() (некоторые свойства)
Фокус focus() - при некоторых условиях
Текст innerText - может вызвать перекомпоновку

Полный список свойств, вызывающих принудительную компоновку, был составлен Полом Айришем и активно используется разработчиками для диагностики проблем производительности.

Диагностика layout thrashing

[править]

Chrome DevTools Performance

[править]

Для выявления layout thrashing используется панель Performance в Chrome DevTools:

1. Открыть DevTools (F12) → вкладка Performance.

2. Нажать кнопку записи и выполнить действия на странице.

3. Остановить запись и проанализировать временную шкалу.

Признаки layout thrashing в профиле:

  • Множество фиолетовых блоков (Layout) на временной шкале.
  • Красные треугольники в правом верхнем углу задач, указывающие на принудительную перекомпоновку.
  • Длинные задачи (long tasks) продолжительностью более 50 мс.

Chrome DevTools также может показывать предупреждения в консоли:

[Violation] Forced reflow while executing JavaScript took 47ms

Это предупреждение появляется, когда принудительная перекомпоновка занимает значительное время.

Lighthouse и PageSpeed Insights

[править]

Инструмент Lighthouse включает аудит «Избегайте синхронной компоновки» (Forced reflow audit), который выявляет проблемные места на странице и показывает, какой код вызвал принудительную перекомпоновку.

Влияние на Core Web Vitals

[править]

Layout thrashing негативно влияет на 2 ключевые метрики Core Web Vitals:

Влияние на Core Web Vitals
Метрика Влияние
LCP (Largest Contentful Paint) Принудительная перекомпоновка задерживает отображение основного контента, увеличивая время загрузки страницы. JavaScript выполняется дольше, что увеличивает общее время блокировки (Total Blocking Time)
INP (Interaction to Next Paint) Если принудительная перекомпоновка уже выполняется в момент начала взаимодействия пользователя, это увеличивает задержку ввода. Если само взаимодействие вызывает принудительную перекомпоновку, увеличивается время обработки. В обоих случаях INP ухудшается

Как исправить layout thrashing

[править]

1. Группировка чтения и записи

[править]

Основной принцип исправления - отделить операции чтения от операций записи:

// Хороший код - без layout thrashing
const boxes = document.querySelectorAll('.box');

// Этап 1: все операции чтения
const heights = [];
for (let i = 0; i < boxes.length; i++) {
  heights.push(boxes[i].offsetHeight);
}

// Этап 2: все операции записи
for (let i = 0; i < boxes.length; i++) {
  boxes[i].style.width = `${heights[i] * 0.5}px`;
}

2. Агрегация чтения геометрических данных

[править]

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

// Плохо: изменение → чтение в цикле
for (const image of images) {
  const slide = addSlide(image);
  const size = slide.clientWidth;  // forced reflow на каждой итерации
  applyStyles(slide, size);
}

// Хорошо: группировка
// 1. Создаём все слайды
const slides = [];
for (const image of images) {
  slides.push(addSlide(image));
}

// 2. Измеряем все размеры
const sizes = slides.map(slide => slide.clientWidth);

// 3. Применяем все стили
for (let i = 0; i < slides.length; i++) {
  applyStyles(slides[i], sizes[i]);
}

3. Использование requestAnimationFrame

[править]

Если изменения необходимо выполнять асинхронно, можно использовать requestAnimationFrame, который гарантирует выполнение кода перед следующей отрисовкой, позволяя браузеру сгруппировать изменения.

4. Отложенная загрузка для некритичных элементов

[править]

Для элементов, которые не видны на первом экране, можно отложить их обработку до завершения первоначального рендеринга с помощью setTimeout или requestIdleCallback.

5. Использование CSS вместо JavaScript

[править]

В некоторых случаях можно вообще отказаться от JavaScript-вычислений, используя современные возможности CSS (flexbox, grid, transforms), которые не вызывают перекомпоновку при анимациях.

Связанные термины

[править]