GPU-Compositing und Layer: Animationen ohne Jank
60fps
ms
Performance · Rendering · GPU · Compositing
GPU-Compositing und Layer: Animationen ohne Jank
Wie der Browser Compositor-Layer nutzt, um 60fps zu halten

Ruckelnde Animationen entstehen fast immer, weil der Browser bei jedem Frame Layout und Paint neu berechnen muss, statt die Arbeit an die GPU zu delegieren. Dieser Artikel erklärt, wie Compositor-Layer entstehen, warum transform und opacity praktisch kostenlos animierbar sind, wie will-change richtig und falsch eingesetzt wird und wie sich Layer-Probleme mit den Chrome DevTools sichtbar machen und beheben lassen.

14 Min. Lesezeit Compositor · Layers · will-change Chrome DevTools · 60fps

1. Warum Animationen ruckeln: Jank und das Compositing-Versprechen

Jank ist der Fachbegriff für sichtbares Ruckeln, wenn eine Animation nicht mit 60 Bildern pro Sekunde läuft. Bei 60fps bleiben genau 16,6 Millisekunden pro Frame, um Style-Berechnung, Layout, Paint und Composite abzuschließen, bevor der nächste Frame ansteht. Überschreitet ein einzelner Frame dieses Budget, wird er verworfen oder verspätet angezeigt, und das Auge nimmt einen Ruckler wahr. Klassische Animationen, die Eigenschaften wie top, left, width oder margin verändern, zwingen den Browser bei jedem einzelnen Frame durch die komplette Rendering-Pipeline, inklusive teurer Layout-Neuberechnung.

Compositing löst genau dieses Problem, indem bestimmte Eigenschaften keine Neuberechnung von Layout oder Paint auslösen, sondern ausschließlich auf der GPU als Textur-Transformation ausgeführt werden. Der Browser zerlegt die Seite dazu in mehrere Compositor-Layer, jeweils als eigene gerenderte Bitmap gespeichert, die unabhängig voneinander verschoben, skaliert oder ein- und ausgeblendet werden können. Dieser Artikel erklärt, wie diese Layer entstehen, wann sie helfen und wann sie durch falschen Einsatz von will-change selbst zum Performance-Problem werden.

2. Der Compositor-Thread: Rendering getrennt vom Main Thread

Moderne Browser wie Chrome trennen die Rendering-Pipeline in mehrere Threads. Der Main Thread führt JavaScript aus, berechnet Styles, Layout und Paint und erzeugt daraus Zeichenbefehle. Diese Zeichenbefehle werden an den separaten Compositor-Thread übergeben, der die eigentliche Bildschirmausgabe zusammensetzt und dabei direkt mit der GPU kommuniziert. Diese Trennung ist der Kern des Compositing-Modells: Solange eine Animation ausschließlich Eigenschaften betrifft, die der Compositor-Thread selbst verarbeiten kann, muss der Main Thread pro Frame nicht erneut aktiv werden.

Der praktische Vorteil zeigt sich genau dann, wenn der Main Thread durch lange JavaScript-Tasks blockiert ist, etwa durch eine teure Produktfilterung oder ein synchrones Analytics-Skript. Eine reine transform- oder opacity-Animation läuft in diesem Fall trotzdem butterweich weiter, weil der Compositor-Thread unabhängig vom Main Thread arbeitet und nicht auf dessen Event-Loop wartet. Animationen, die hingegen Layout auslösen, frieren in genau diesem Szenario ein, bis der Main Thread wieder frei wird.

3. Layer-Promotion: Wie der Browser über eigene Compositor-Layer entscheidet

Ein Element bekommt einen eigenen Compositor-Layer entweder explizit oder implizit. Explizite Auslöser sind unter anderem will-change: transform, eine aktive CSS-Animation oder -Transition auf transform/opacity, position: fixed in bestimmten Stacking-Kontexten, video- und canvas-Elemente sowie Eigenschaften wie filter und backdrop-filter. Diese Fälle sind bewusste Signale an den Browser: Dieses Element wird sich unabhängig von seiner Umgebung verändern, also lohnt sich eine eigene Textur.

Implizite Promotion passiert, wenn der Browser selbst erkennt, dass ein Element mit einem bereits promoteten Element überlappt und deshalb ebenfalls einen eigenen Layer braucht, um korrektes Stacking beim Compositing zu garantieren. Diese automatische Kaskade ist die häufigste Ursache für unerwartet viele Layer. Die Chrome DevTools zeigen im Layers-Panel unter "Compositing Reasons" pro Layer genau, welcher der beiden Auslöser zutrifft, was bei der Fehlersuche entscheidend hilft.

4. transform und opacity: Die einzigen wirklich günstigen Animationseigenschaften

transform und opacity gelten als compositor-only, weil eine Änderung dieser Eigenschaften weder die Position noch die Größe anderer Elemente im Dokumentfluss beeinflusst. Der Browser muss also weder Layout neu berechnen noch die betroffene Fläche neu zeichnen (Paint). Stattdessen wendet der Compositor-Thread die neue Transformationsmatrix direkt auf die bereits vorhandene, gerenderte Bitmap des Layers an, eine reine GPU-Operation, die typischerweise deutlich unter einer Millisekunde pro Frame kostet.

Eigenschaften wie top, left, width, height oder margin lösen dagegen zwingend Layout aus, weil sie die Geometrie des Elements und potenziell aller nachfolgenden Geschwister-Elemente verändern. Auf einer Kategorieseite mit mehreren tausend DOM-Knoten kann eine einzelne Layout-Neuberechnung 10 bis 20 Millisekunden kosten, allein damit ist das 16,6-Millisekunden-Budget für 60fps aufgebraucht, noch bevor Paint und Composite überhaupt stattfinden.


/* BAD: animating layout-triggering properties causes reflow on every frame */
.modal-bad {
  position: absolute;
  top: 100px;
  left: -400px;
  width: 320px;
  transition: left 0.3s ease-out, width 0.3s ease-out;
}

.modal-bad.is-open {
  left: 40px;
  width: 360px;
}

/* Every frame of this transition triggers: */
/* 1. Layout (recalculate geometry of the modal and all affected siblings) */
/* 2. Paint (repaint the modal and everything it displaced) */
/* 3. Composite (upload the new bitmap to the GPU) */
/* On a page with a few thousand DOM nodes, layout alone can cost 10-20ms, */
/* blowing well past the 16.6ms frame budget for 60fps. */

/* GOOD: transform and opacity skip Layout and Paint entirely */
.modal-good {
  position: absolute;
  top: 100px;
  left: 40px;
  width: 360px;
  opacity: 0;
  transform: translateX(-440px) scale(0.96);
  transition: transform 0.3s ease-out, opacity 0.3s ease-out;
}

.modal-good.is-open {
  opacity: 1;
  transform: translateX(0) scale(1);
}

/* The compositor thread recomputes the transform matrix directly on the */
/* already-painted layer texture. No Layout, no Paint, just a Composite step. */

5. will-change richtig einsetzen: Ein Hinweis, kein Zauberwort

will-change ist ausdrücklich als Hinweis an den Browser gedacht, nicht als Garantie oder Zauberwort für bessere Performance. Der korrekte Einsatz besteht darin, will-change kurz bevor eine Animation beginnt zu setzen, damit der Browser die Layer-Promotion vorab vornehmen kann, statt sie erst beim ersten Frame der Animation nachzuholen, was sonst zu einem sichtbaren Ruckler beim Animationsstart führt.

Sinnvolle Werte sind die konkreten Eigenschaften, die sich tatsächlich ändern werden, etwa will-change: transform oder will-change: transform, opacity, niemals der generische Wildcard-Wert will-change: auto als Dauerzustand oder gar will-change ohne konkrete Eigenschaftsangabe. Die CSS-Spezifikation warnt explizit davor, will-change dauerhaft auf viele Elemente zu setzen, weil jede Promotion echten GPU-Speicher bindet, unabhängig davon, ob gerade animiert wird.


// GOOD: add will-change just before the animation starts,
// remove it as soon as the animation ends to free GPU memory
const modal = document.querySelector('.modal-good');

function openModal() {
  // Give the browser a heads-up one frame before the transition begins
  modal.style.willChange = 'transform, opacity';

  requestAnimationFrame(() => {
    requestAnimationFrame(() => {
      modal.classList.add('is-open');
    });
  });
}

modal.addEventListener('transitionend', () => {
  // Release the compositor layer once the animation is done
  modal.style.willChange = 'auto';
}, { once: true });

6. will-change falsch einsetzen: Speicher- und GPU-Kosten

Jeder promotete Layer benötigt eine eigene Backing-Bitmap in GPU-Speicher, deren Größe sich aus Breite mal Höhe mal 4 Byte pro Pixel (RGBA) mal dem Quadrat des Device-Pixel-Ratios ergibt. Ein vollflächiger Layer auf einem Full-HD-Viewport kostet bei einfacher Auflösung bereits rund 8 Megabyte, auf einem Retina-Display mit Device-Pixel-Ratio 2 wegen der quadratischen Skalierung schon etwa 32 Megabyte, allein für diesen einen Layer.

Wird will-change dauerhaft auf viele Elemente gesetzt, etwa auf jede Produktkarte einer Kategorieseite, summieren sich diese Kosten schnell auf hunderte Megabyte GPU-Speicher, obwohl zu jedem Zeitpunkt nur ein einziges Element tatsächlich animiert wird. Auf mobilen Geräten mit begrenztem GPU-Speicher führt das nicht nur zu spürbarem Ruckeln, sondern im Extremfall zum Abstürzen des Tabs oder dazu, dass der Browser Layer wieder zwangsweise demoted und die ursprünglich gewünschte Beschleunigung komplett verpufft.


/* BAD: will-change on a parent promotes every overlapping child too, */
/* and leaving it permanent keeps all of them resident in GPU memory */
.product-grid {
  will-change: transform;
}

.product-grid .product-card {
  /* Each card overlaps its animated siblings during hover transitions, */
  /* so the browser promotes all of them to their own layer as well */
  transition: transform 0.2s ease-out;
}

.product-grid .product-card:hover {
  transform: translateY(-4px);
}

/* Result on a category page with 60 product cards: */
/* ~60 persistent compositor layers, each holding its own GPU-backed */
/* bitmap, even though only one card is being hovered at a time. */

7. Layer-Explosion: Symptome, Ursachen, Diagnose

Layer-Explosion beschreibt den Zustand, in dem eine Seite plötzlich Dutzende oder Hunderte Compositor-Layer erzeugt, meist ausgelöst durch will-change auf einem Container-Element, das dadurch alle überlappenden Kind-Elemente implizit mitpromotet, oder durch viele gleichzeitig animierte Geschwister-Elemente mit unterschiedlichem z-index innerhalb desselben Stacking-Kontexts.

Das paradoxe Ergebnis: Statt schneller zu werden, wird die Seite langsamer, weil der Compositor-Thread bei jedem Frame alle Layer-Texturen verwalten und zur GPU hochladen muss, und die Upload-Bandbreite selbst zum Flaschenhals wird. Symptome sind ein spürbar trägeres Scrollen trotz reiner Compositor-Animationen sowie ein stark erhöhter GPU-Speicherverbrauch im Task-Manager des Browsers, der sich unter chrome://gpu oder in den DevTools direkt beobachten lässt.

8. DevTools: Layers-Panel und "Layer borders" richtig lesen

Über More Tools > Layers öffnen die Chrome DevTools eine dreidimensionale Visualisierung aller aktiven Compositor-Layer der Seite. Ein Klick auf einen einzelnen Layer zeigt dessen exakte Speichergröße in Megabyte sowie unter "Compositing Reasons" den konkreten Grund für die Promotion, etwa "Has a will-change: transform compositing reason" oder "Overlaps other composited content".

Für schnelle Live-Diagnose direkt auf der Seite eignet sich die Rendering-Flag-Leiste besser als das Layers-Panel: Über Cmd/Ctrl+Shift+P und "Show Rendering" lässt sich "Layer borders" aktivieren, das jeden promoteten Layer live mit einem orangenen Rahmen und semitransparenten Streifen an den Rändern markiert. In Kombination mit "Paint flashing", das repainte Bereiche grün einfärbt, zeigt sich sofort, ob eine Animation tatsächlich compositor-only läuft oder ungewollt Paint auslöst.


<!-- Hyva phtml: Alpine.js dropdown using transform/opacity, never width/height -->
<div x-data="{ open: false }" class="relative">
    <button @click="open = !open" class="px-4 py-2 font-semibold">
        Filter
    </button>

    <div
        x-show="open"
        x-transition:enter="transition ease-out duration-200"
        x-transition:enter-start="opacity-0 -translate-y-2 scale-95"
        x-transition:enter-end="opacity-100 translate-y-0 scale-100"
        x-transition:leave="transition ease-in duration-150"
        x-transition:leave-start="opacity-100 translate-y-0 scale-100"
        x-transition:leave-end="opacity-0 -translate-y-2 scale-95"
        class="absolute mt-2 w-64 bg-white rounded-xl shadow-lg origin-top"
    >
        <!-- Filter content -->
    </div>
</div>

9. Praxis-Patterns für ruckelfreie 60fps-Animationen

Die wichtigste Grundregel: Bewegung, Skalierung und Sichtbarkeit ausschließlich über transform und opacity animieren, niemals über top, left, width, height, margin oder box-shadow. Wo eine dritte Dimension nötig ist, etwa um explizit GPU-Beschleunigung zu erzwingen, hilft translate3d oder eine vollständige matrix3d-Transformation, auch wenn moderne Browser 2D-transform-Animationen inzwischen meist von sich aus korrekt promoten.

Ebenso wichtig ist das Aufräumen: will-change sollte per JavaScript kurz vor der Animation gesetzt und über einen transitionend- oder animationend-Listener direkt danach wieder auf auto zurückgesetzt werden, damit der Layer nicht dauerhaft GPU-Speicher belegt. In Alpine.js-Komponenten gilt dieselbe Regel für x-transition-Klassen: Sie sollten ausschließlich Tailwind-Utilities wie opacity-0, scale-95 oder -translate-y-2 kombinieren, niemals Utilities, die width, height oder Positionierungs-Offsets animieren.

Pattern Rendering-Pfad Ungünstig Empfohlen
Position Layout + Paint + Composite top/left animieren transform: translate()
Größe Layout + Paint + Composite width/height animieren transform: scale()
Schatten Paint bei jedem Frame box-shadow animieren opacity-basierter Schatten-Layer
will-change-Lebensdauer GPU-Speicher dauerhaft belegt will-change permanent gesetzt will-change per JS gesetzt/entfernt
Layer-Anzahl GPU-Speicher multipliziert sich Alle Elemente promoted (Layer-Explosion) Nur animierte Elemente promoted

In der Praxis hängen Layer-Anzahl und GPU-Speicherverbrauch direkt zusammen: Jede zusätzliche Promotion, ob gewollt über will-change oder ungewollt durch Überlappung, kostet Speicher und Verwaltungsaufwand im Compositor-Thread. Wer konsequent nur transform und opacity animiert und will-change gezielt und kurzlebig einsetzt, hält die Layer-Anzahl niedrig und die Animation bei stabilen 60fps.

Mironsoft

GPU-Performance, Compositing und Rendering-Optimierung für Magento-Shops

Animationen ohne Jank umsetzen?

Wir analysieren die Compositor-Layer eures Magento- und Hyvä-Shops, identifizieren Layer-Explosion und falsch eingesetztes will-change und bringen eure Animationen zuverlässig auf 60fps.

Layer-Audit

DevTools-Analyse aller Compositor-Layer und ihrer Promotion-Gründe

Animations-Refactoring

Umstellung auf transform/opacity und sauberes will-change-Handling

GPU-Speicher-Monitoring

Kontinuierliche Kontrolle von Layer-Anzahl und Speicherverbrauch

10. Zusammenfassung

GPU-Compositing löst das Grundproblem von Jank, indem transform- und opacity-Animationen komplett am Main Thread vorbei direkt auf der GPU laufen und dabei das 16,6-Millisekunden-Frame-Budget für 60fps zuverlässig einhalten. Voraussetzung ist, dass der Browser das animierte Element rechtzeitig auf einen eigenen Compositor-Layer promotet, entweder implizit durch Überlappungserkennung oder explizit über will-change als gezielten, kurzlebigen Hinweis.

Der entscheidende Unterschied zwischen einer performanten und einer kontraproduktiven Layer-Strategie liegt im Lebenszyklus: will-change gehört kurz vor die Animation und direkt danach wieder entfernt, niemals dauerhaft auf viele Elemente gesetzt. Die Chrome DevTools mit Layers-Panel und dem Rendering-Flag "Layer borders" machen Layer-Anzahl, Speicherverbrauch und Promotion-Gründe jederzeit sichtbar und sind das zuverlässigste Werkzeug, um Layer-Explosion frühzeitig zu erkennen, bevor sie produktiv wird.

GPU-Compositing und Layer - Das Wichtigste auf einen Blick

Frame-Budget: 16,6ms

Bei 60fps müssen Style, Layout, Paint und Composite in 16,6ms pro Frame abgeschlossen sein.

transform & opacity animieren

Einzige Eigenschaften, die Layout und Paint vollständig überspringen und rein auf der GPU laufen.

will-change gezielt einsetzen

Kurz vor der Animation setzen, direkt danach per JS wieder auf auto zurücksetzen.

DevTools zur Diagnose nutzen

Layers-Panel und Rendering-Flag Layer borders zeigen Layer-Anzahl, Speicher und Promotion-Gründe.

11. FAQ: GPU-Compositing und Layer

1Was ist Jank und warum entsteht es bei Animationen?
Sichtbares Ruckeln, wenn ein Frame das 16,6-Millisekunden-Budget für 60fps überschreitet. Entsteht meist durch Layout-auslösende Eigenschaften wie top oder width statt transform und opacity.
2Was ist der Unterschied zwischen Main Thread und Compositor Thread?
Main Thread führt JavaScript, Style, Layout und Paint aus. Compositor-Thread setzt das finale Bild unabhängig zusammen und kommuniziert direkt mit der GPU.
3Welche CSS-Eigenschaften lösen ausschließlich Compositing aus?
Nur transform und opacity. Beide werden als reine GPU-Operation auf der bereits gerenderten Layer-Textur ausgeführt, ohne Layout oder Paint.
4Wann promotet der Browser ein Element automatisch zu einem eigenen Layer?
Implizit bei Überlappung mit einem bereits promoteten Layer. Explizit durch will-change, aktive transform/opacity-Transitions, video/canvas oder filter/backdrop-filter.
5Wie setze ich will-change korrekt ein?
Kurz vor Animationsstart per JavaScript setzen, mit konkreten Eigenschaften wie transform, und direkt nach Animationsende wieder auf auto zurücksetzen.
6Welche Nachteile hat dauerhaftes will-change?
Bindet dauerhaft GPU-Speicher pro Element. Auf vielen Elementen gesetzt führt das zu Speicherdruck, Ruckeln und im Extremfall zu Tab-Abstürzen auf mobilen Geräten.
7Was ist Layer-Explosion und wie erkenne ich sie?
Dutzende bis Hunderte unnötig promoteter Layer, meist durch will-change auf Containern. Erkennbar an trägem Scrollen und hohem GPU-Speicherverbrauch.
8Wie nutze ich das Chrome DevTools Layers-Panel?
Über More Tools > Layers öffnen. Ein Klick auf einen Layer zeigt Speichergröße und unter Compositing Reasons den Grund der Promotion.
9Was zeigt die Option Layer borders in den Rendering-Flags?
Markiert jeden Compositor-Layer live mit einem orangenen Rahmen. Zusammen mit Paint flashing zeigt sich sofort, ob eine Animation Paint auslöst.
10Wie baue ich eine 60fps-Animation ohne Jank?
Nur transform und opacity animieren, will-change gezielt setzen und entfernen, und mit Layer borders in den DevTools verifizieren.