Kodokon kodokon.com

Die Rendering-Pipeline: Reflow, Repaint, Compositing

Verstehe die vier Phasen des Browser-Renderings, damit du genau weißt, welche CSS-Eigenschaften teuer sind und wie will-change unter der Haube funktioniert.

11 Min. · 3 Fragen

Diese Lektion in Kodokon öffnen

Der Browser verwandelt dein CSS über eine vierstufige Pipeline in Pixel: Style (Werte berechnen), Layout (Geometrie, auch Reflow genannt), Paint (das Rastern in Pixel, der Repaint) und Composite (das Zusammensetzen der Ebenen auf der GPU). Die goldene Regel: Je früher die Phase ist, die du berührst, desto höher die Rechnung, denn jede folgende Phase wird erneut durchlaufen. Ändern von width oder top löst Layout + Paint + Composite aus. Ändern von color oder box-shadow überspringt das Layout, malt aber neu. Nur transform und opacity können allein vom Compositor-Thread verarbeitet werden: Die Animation bleibt flüssig, selbst wenn der Hauptthread von JavaScript blockiert ist.

CSS
.slide-bad {
  position: absolute;
  transition: left 0.3s ease;
}
.slide-bad:hover { left: 120px; }

.slide-good {
  transition: transform 0.3s ease;
}
.slide-good:hover { transform: translateX(120px); }
Zwei optisch identische Animationen: Die erste erzwingt bei jedem Frame einen Reflow, die zweite bleibt auf dem Compositor.

Der Browser ist faul: Er bündelt deine Stil-Schreibvorgänge und berechnet das Layout erst beim nächsten Refresh neu. Doch manche JavaScript-Lesevorgänge - offsetHeight, getBoundingClientRect(), scrollTop, getComputedStyle() auf einer geometrischen Eigenschaft - verlangen aktuelle Werte. Wenn ein Schreibvorgang aussteht, muss der Browser sofort und synchron neu berechnen: Das ist ein erzwungener Reflow. Lese- und Schreibvorgänge in einer Schleife abzuwechseln erzeugt Layout-Thrashing: ein vollständiger Reflow pro Durchlauf statt eines einzigen für den ganzen Stapel.

JAVASCRIPT
const items = document.querySelectorAll('.item');

items.forEach((item) => {
  const h = item.offsetHeight;
  item.style.height = h * 2 + 'px';
});

const heights = [...items].map((i) => i.offsetHeight);
items.forEach((item, index) => {
  item.style.height = heights[index] * 2 + 'px';
});
Version 1: ein erzwungener Reflow pro Element. Version 2: erst alle Lesevorgänge, dann alle Schreibvorgänge - ein einziger Reflow.

Compositing zerlegt die Seite in Ebenen, die unabhängig gerastert und von der GPU zusammengesetzt werden. will-change: transform fordert an, dass das Element vor der Animation auf eine eigene Ebene gehoben wird, was die teure Rasterung im ersten Frame vermeidet. Eine oft übersehene Feinheit der Spezifikation: will-change muss dieselben Nebenwirkungen erzeugen wie die Eigenschaft, die es ankündigt. So erzeugt will-change: transform einen Stacking-Context und macht das Element zu einem Containing Block für seine position: fixed-Nachfahren - genau wie ein echtes transform. Ein simpler Performance-Hinweis kann also deine Stapelreihenfolge oder deine fixierten Elemente zerbrechen.

JAVASCRIPT
const panel = document.querySelector('.panel');

panel.addEventListener('pointerenter', () => {
  panel.style.willChange = 'transform';
});

panel.addEventListener('transitionend', () => {
  panel.style.willChange = 'auto';
});
Das von der Spezifikation empfohlene Muster: kurz vor der Animation hochstufen, kurz danach freigeben.

Wissenscheck

Stelle sicher, dass du die wichtigsten Punkte dieser Lektion behalten hast.

  1. Welche Animation kann vollständig vom Compositor-Thread verarbeitet werden, ohne Reflow oder Repaint?
    • height von 0 auf 200px
    • margin-left von 0 auf 100px
    • transform: translateX(100px)
    • box-shadow beim Hover
  2. Warum ist das Lesen von offsetHeight in einer Schleife, die zugleich Stile ändert, so teuer?
    • Jeder Lesevorgang erzwingt einen synchronen Reflow, wenn ein Schreibvorgang aussteht
    • offsetHeight löst bei jedem Zugriff einen GPU-Repaint aus
    • Die Eigenschaft wird von der JavaScript-Engine nie zwischengespeichert
  3. Welche Nebenwirkung teilt will-change: transform mit einem echten transform?
    • Es deaktiviert die CSS-Transitions des Elements
    • Es erzeugt einen Stacking-Context und einen Containing Block für position: fixed
    • Es zwingt das Element in display: block
    • Es verdoppelt die Rasterauflösung