Layout thrashing (zwany też wymuszonym synchronicznym układem lub forced reflow) występuje, gdy JavaScript wielokrotnie naprzemiennie zapisuje zmiany do drzewa DOM i odczytuje właściwości geometryczne, zmuszając przeglądarkę do synchronicznego przeliczania układu strony.
Wyzwalacze unieważnienia układu (Layout Invalidation)
Następujące właściwości i metody DOM wymuszają synchroniczne przeliczenie obliczonej geometrii za każdym razem, gdy układ został unieważniony przez wcześniejsze modyfikacje DOM.
Właściwości i metody elementów
Geometria i wymiary pudełka
clientHeight,clientWidthclientLeft,clientTopoffsetHeight,offsetWidthoffsetLeft,offsetTopoffsetParentscrollHeight,scrollWidthscrollLeft,scrollTop
Zapytania o ramki ograniczające
getBoundingClientRect()getClientRects()
Operacje przewijania
scrollIntoView()scrollIntoViewIfNeeded()scrollByLines()scrollByPages()
Tekst i fokus
innerTextouterTextfocus()(wymusza aktualizację obramowania fokusu i przewijania)
Właściwości zdarzeń myszy
Odczyt określonych współrzędnych w obiekcie MouseEvent może uruchamiać przeliczenie układu w niektórych silnikach przeglądarek:
layerX,layerYoffsetX,offsetY
Metody i właściwości Window
window.getComputedStyle()(podczas odpytywania wartości zależnych od układu, np. width, height, margin czy transform)window.scrollBy()window.scrollTo()window.scrollX,window.scrollY
Ramka, dokument i obraz
- Odczyt
widthlubheightobrazu przed ustabilizowaniem układu - Dostęp do
document.body.scrollHeight
Operacje jQuery
Wiele metod pomocniczych jQuery odczytuje pod spodem obliczone wartości układu:
// Wywołuje reflow
$(elem).offset();
$(elem).offsetParent();
$(elem).position();
$(elem).scrollLeft();
$(elem).scrollTop();
$(elem).css('width'); // Każde obliczenie stylu zależne od układu
$(elem).text();
// Pseudoselektory sprawdzające geometryczną widoczność
$(':hidden');
$(':visible');
$(':contains');
Dobre praktyki zapobiegania Layout Thrashing
1. Grupuj odczyty i zapisy
Zawsze najpierw wykonaj wszystkie pomiary DOM, a następnie zgrupuj razem wszystkie modyfikacje DOM.
// ŹLE: Przeplatanie odczytu i zapisu wymusza przeliczenie układu w każdej iteracji
elements.forEach((el) => {
const width = el.clientWidth; // ODCZYT (wymusza układ)
el.style.width = width * 2 + 'px'; // ZAPIS (unieważnia układ)
});
// DOBRZE: Zbiorczy odczyt, a potem zbiorczy zapis
const widths = elements.map((el) => el.clientWidth); // ZBIORCZY ODCZYT
elements.forEach((el, index) => {
el.style.width = widths[index] * 2 + 'px'; // ZBIORCZY ZAPIS
});
2. Używaj requestAnimationFrame
Zaplanuj zapis stylów w kolejnej klatce animacji, aby przeglądarka zoptymalizowała cykl renderowania:
const height = element.clientHeight; // Odczyt w bieżącej fazie
window.requestAnimationFrame(() => {
element.style.height = height + 50 + 'px'; // Zapis w następnej klatce
});
3. Używaj CSS Transforms oraz Opacity
Animuj właściwości CSS obsługiwane przez wątek kompozytora (transform, opacity) zamiast właściwości układu (top, left, width, height).