Layout thrashing (также известный как принудительная синхронная компоновка или forced reflow) возникает, когда JavaScript многократно чередует запись в DOM и чтение геометрических свойств, заставляя браузер синхронно пересчитывать раскладку страницы.
Триггеры пересчёта компоновки (Layout Invalidation)
Перечисленные ниже свойства и методы DOM вызывают принудительный синхронный перерасчёт геометрии, если компоновка была сброшена предшествующими изменениями DOM.
Свойства и методы элементов
Геометрия и размеры блока
clientHeight,clientWidthclientLeft,clientTopoffsetHeight,offsetWidthoffsetLeft,offsetTopoffsetParentscrollHeight,scrollWidthscrollLeft,scrollTop
Запрос ограничивающих прямоугольников
getBoundingClientRect()getClientRects()
Операции прокрутки
scrollIntoView()scrollIntoViewIfNeeded()scrollByLines()scrollByPages()
Текст и фокус
innerTextouterTextfocus()(вызывает пересчёт рамки фокуса и прокрутки)
Свойства событий мыши
Чтение некоторых координат у объекта MouseEvent в ряде движков браузера может запускать перерасчёт компоновки:
layerX,layerYoffsetX,offsetY
Методы и свойства Window
window.getComputedStyle()(при запросе зависящих от раскладки значений: ширины, высоты, отступов, трансформаций)window.scrollBy()window.scrollTo()window.scrollX,window.scrollY
Фреймы, документ и изображения
- Чтение
widthилиheightизображения до стабилизации раскладки - Обращение к
document.body.scrollHeight
Операции jQuery
Многие вспомогательные методы jQuery под капотом запрашивают вычисленные параметры компоновки:
// Вызывает reflow
$(elem).offset();
$(elem).offsetParent();
$(elem).position();
$(elem).scrollLeft();
$(elem).scrollTop();
$(elem).css('width'); // Любое вычисление стилей, зависящее от компоновки
$(elem).text();
// Псевдоселекторы, проверяющие геометрическую видимость
$(':hidden');
$(':visible');
$(':contains');
Лучшие практики предотвращения Layout Thrashing
1. Пакетное чтение и пакетная запись
Сначала выполните все измерения в DOM, а затем сгруппируйте все изменения DOM вместе.
// ПЛОХО: чередование чтения и записи вызывает перерасчёт на каждой итерации
elements.forEach((el) => {
const width = el.clientWidth; // ЧТЕНИЕ (принудительный layout)
el.style.width = width * 2 + 'px'; // ЗАПИСЬ (инвалидация layout)
});
// ХОРОШО: сначала пакетное чтение, затем пакетная запись
const widths = elements.map((el) => el.clientWidth); // ПАКЕТНОЕ ЧТЕНИЕ
elements.forEach((el, index) => {
el.style.width = widths[index] * 2 + 'px'; // ПАКЕТНАЯ ЗАПИСЬ
});
2. Используйте requestAnimationFrame
Планируйте обновление стилей на следующий кадр анимации, чтобы браузер оптимизировал цикл рендеринга:
const height = element.clientHeight; // Чтение в текущей фазе
window.requestAnimationFrame(() => {
element.style.height = height + 50 + 'px'; // Запись в следующем кадре
});
3. Используйте CSS Transforms и Opacity
Анимируйте свойства, обрабатываемые потоком композитора (transform, opacity), вместо свойств раскладки (top, left, width, height).