Erst lesen, dann schreiben: Layout Thrashing vermeiden
Wer Lese- und Schreibzugriffe auf das DOM in einer Schleife mischt, zwingt den Browser, das Layout immer wieder neu zu berechnen. Besser bündeln.
Ändert man einen Style, berechnet der Browser das Layout nicht sofort neu. Er markiert es als veraltet und wartet bis zum nächsten Frame. Liest man danach aber einen Geometriewert wie offsetWidth, bleibt ihm keine Wahl: Er muss das Layout sofort neu berechnen, um eine korrekte Antwort zu liefern. Passiert das in einer Schleife, spricht man von Layout Thrashing.
Die langsame Variante
for (const el of items) {
const width = el.parentNode.offsetWidth; // lesen: erzwingt Layout
el.style.width = width / 2 + 'px'; // schreiben: macht Layout ungültig
}
Jeder Durchlauf macht das Layout ungültig, und der nächste erzwingt es erneut. Bei ein paar hundert Elementen kostet das schnell einige Dutzend Millisekunden.
Die schnelle Variante
const widths = items.map((el) => el.parentNode.offsetWidth); // alle Lesezugriffe
items.forEach((el, i) => { // alle Schreibzugriffe
el.style.width = widths[i] / 2 + 'px';
});
Gleiches Ergebnis, ein einziges Layout. Die Regel ist einfach: erst alles messen, was man braucht, dann ändern.
Was als Lesezugriff zählt
Die üblichen Verdächtigen sind Eigenschaften und Methoden, die Geometrie oder berechnete Werte liefern:
offsetTop,offsetWidth,offsetHeightund VerwandteclientWidth,clientHeight,scrollTop,scrollHeightgetBoundingClientRect()getComputedStyle(), wenn man einen layoutabhängigen Wert liestinnerText, das ebenfalls vom Layout abhängt
Erkennen
Im Performance-Panel der Entwicklertools zeigt sich Thrashing als viele kleine violette Layout-Blöcke innerhalb eines einzigen Tasks, oft mit einer Warnung zu Forced Reflow, die auf die verantwortliche Codezeile verweist. Muss der Code auf Größenänderungen reagieren, ist ein ResizeObserver meist besser, als selbst zu messen.