Что такое Force layout?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое Force layout
Force layout (принудительная разметка) — это нежелательное поведение браузера, когда произойдет принудительный recalc стилей и перерисовка страницы из-за обращения к макет-зависимым свойствам в JavaScript. Force layout замораживает JavaScript выполнение, значительно замедляя производительность приложения.
Почему это происходит
Браузер использует асинхронный подход для выполнения изменений:
- JavaScript изменяет стили (например,
element.style.width = "100px") - Браузер добавляет эти изменения в очередь
- Обычно браузер применяет все изменения разом на следующем фрейме
Но если JavaScript обращается к свойству, которое зависит от макета (например, element.offsetWidth), браузер вынужден немедленно:
- Пересчитать стили (recalc)
- Пересчитать макет (layout reflow)
- Перерисовать (repaint)
Это вызывает блокировку JavaScript и может привести к потере кадров.
Свойства, которые вызывают Force Layout
Свойства размеров и позиций
// Все эти свойства вызывают force layout:
const width = element.offsetWidth; // Ширина с padding/border
const height = element.offsetHeight; // Высота с padding/border
const left = element.offsetLeft; // Позиция слева
const top = element.offsetTop; // Позиция сверху
const parent = element.offsetParent; // Родитель для позиционирования
const scrollWidth = element.scrollWidth; // Ширина скролла
const scrollHeight = element.scrollHeight; // Высота скролла
const scrollTop = element.scrollTop; // Текущая позиция скролла
const scrollLeft = element.scrollLeft; // Позиция скролла слева
Методы
// Методы getBoundingClientRect и др.
const rect = element.getBoundingClientRect(); // FORCE LAYOUT!
const coords = element.getClientRects(); // FORCE LAYOUT!
Вычисленные стили
// Получение вычисленных стилей
const style = window.getComputedStyle(element);
const display = style.display; // FORCE LAYOUT!
const width = style.width; // FORCE LAYOUT!
Свойства Window
const width = window.innerWidth; // FORCE LAYOUT!
const height = window.innerHeight; // FORCE LAYOUT!
const scrollY = window.scrollY; // FORCE LAYOUT!
Классический пример проблемы
// ПЛОХО: Force layout в цикле
const elements = document.querySelectorAll(".item");
for (let i = 0; i < elements.length; i++) {
elements[i].style.width = (100 + i * 10) + "px"; // Добавляем в очередь
const width = elements[i].offsetWidth; // FORCE LAYOUT!
console.log(`Элемент ${i}: ${width}px`);
}
// Результат: N force layouts для N элементов (ОЧЕНЬ МЕДЛЕННО)
Решение 1: Разделить чтение и запись
// ХОРОШО: Сначала читаем, потом пишем
const elements = document.querySelectorAll(".item");
const widths = [];
// Этап 1: Читаем все значения
for (let i = 0; i < elements.length; i++) {
widths.push(elements[i].offsetWidth); // Один force layout
}
// Этап 2: Пишем значения (без force layout)
for (let i = 0; i < elements.length; i++) {
elements[i].style.width = (widths[i] + 10) + "px";
}
Решение 2: Использовать requestAnimationFrame
// Использовать rAF для группировки операций
const elements = document.querySelectorAll(".item");
requestAnimationFrame(() => {
// Все чтение
const widths = elements.map(el => el.offsetWidth);
// Браузер обработает рAF после измерения макета
requestAnimationFrame(() => {
// Все письмо
widths.forEach((w, i) => {
elements[i].style.width = (w + 10) + "px";
});
});
});
Решение 3: Использовать ResizeObserver или IntersectionObserver
// Для отслеживания изменений размера без force layout
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
console.log("Размер изменился:", entry.contentRect);
// Применяем изменения
}
});
observer.observe(element);
Практический пример в React
// ПЛОХО
function Component() {
const [positions, setPositions] = React.useState([]);
React.useEffect(() => {
const elements = document.querySelectorAll(".item");
const newPositions = [];
for (const el of elements) {
newPositions.push({
x: el.offsetLeft, // FORCE LAYOUT в цикле!
y: el.offsetTop, // FORCE LAYOUT в цикле!
});
}
setPositions(newPositions);
}, []);
}
// ХОРОШО
function Component() {
const [positions, setPositions] = React.useState([]);
React.useEffect(() => {
const elements = document.querySelectorAll(".item");
// Один force layout для всех элементов
const newPositions = Array.from(elements).map(el => ({
x: el.offsetLeft,
y: el.offsetTop,
}));
setPositions(newPositions);
}, []);
}
Инструменты для диагностики
Chrome DevTools Performance
- Открыть DevTools (F12)
- Перейти на вкладку Performance
- Записать профиль
- Поискать "Recalculate Style" и "Layout" в временной шкале
- Если видите много коротких блоков, это force layout
Более конкретно
// Можно добавить логирование
function getOffsetsWithLogging(element) {
console.time("getOffsets");
const rect = element.getBoundingClientRect();
console.timeEnd("getOffsets");
return rect;
}
Когда Force Layout приемлем
- При инициализации страницы (не в цикле анимации)
- Редко (не в requestAnimationFrame)
- Когда производительность не критична
- При работе с одним элементом
Когда избегать
- В цикле анимации (requestAnimationFrame)
- В цикле обработки множества элементов
- Если нужно 60 FPS (16ms на фрейм)
- При скроллировании
- При изменении размера окна
Практический совет для фронтенда
// Батчинг операций с DOM
function batchDOMOperations(operations) {
// Этап 1: Читаем
const reads = operations.map(op => op.read());
// Этап 2: Пишем
operations.forEach((op, i) => op.write(reads[i]));
}
// Использование
batchDOMOperations([
{
read: () => element.offsetWidth,
write: (width) => {
element.style.transform = `scaleX(${width / 100})`;
}
}
]);
Заключение
Force layout — это один из главных врагов производительности веб-приложений. Понимание того, какие операции его вызывают, и как его избежать, критически важно для написания быстрого кода. Основной принцип: читай сразу всё, потом пиши сразу всё.