will-change und der Compositor Layer

von Henry Zeitler

Animationen auf einer Webseite wirken erst so richtig elegant, wenn sie mit einer Rate von 60 Frames pro Sekunde laufen. Grafische Operationen auf diesem hohen Niveau sind aber wiederum extrem rechenintensiv und müssen deshalb gut vorbereitet werden. Zu diesem Zweck wollen Googles Performance-Gurus Entwicklern ein neues Werkzeug an die Hand geben: die CSS-Eigenschaft will-change.

Dieser Artikel erschien am 11.Dezember 2014 im Webkrauts Advents-Kalender.

Führt ein Browser eine CSS-Transformation oder -Animation aus, so könnte er vor der Entscheidung stehen, diese Operation mit der CPU (Central Processing Unit) oder GPU (Graphics Processing Unit) berechnen zu lassen. »Könnte« deshalb, weil auch moderne Browser in der Regel noch die CPU nutzen. Nur wenige sind clever genug, die Berechnungen einiger dedizierter CSS-Eigenschaften wie z. B. opacity, scale, position und rotation direkt an die GPU zu übergeben.

Aber warum ist es so vorteilhaft, die Berechnungen von grafischen Operationen und deren Rendering von der GPU übernehmen zu lassen? Die Antwort liegt auf der Hand: Die CPU übernimmt die Tasks, die den Rechner und somit die Software antreiben. Und das ist eine Menge Arbeit. Die GPU ist die Spezialistin für grafische Operationen und Rendering-Anfragen. Sie bearbeitet solche Tasks wesentlich schneller als die CPU und kann sie somit auf diesen Gebieten mehr als entlasten. Durch diese Arbeitsteilung können spürbare Performance-Vorteile erzielt werden, wenn anfallende Tasks entsprechend ihrer Art der CPU oder der GPU aktiv zugeordnet werden.

Extrem wichtig ist eine derartige Performance-Optimierung und Delegation (nach wie vor) für mobile Endgeräte. Die CPUs und GPUs sind zwar schon recht leistungsfähig verglichen mit den Standards von vor ein paar Jahren, reichen aber an einen moderneren Desktop-Computer nicht heran.

»Mobile platforms are 5x slower than desktop – with slower CPUs, more constrained memory and lower powered GPUs. That won’t change.«
5 Myths About Mobile Web Performance, Michael Mullany

Das Zusammenspiel von Browser und GPU

Werden nun rechenintensive Operationen zur Entlastung der CPU an spezialisierte Hardware wie z. B. der GPU delegiert, so wird von der Hardwarebeschleunigung bzw. Hardware Acceleration gesprochen. Mit ihrer Hilfe werden dann auch die begehrten Frameraten von 60 fps realisierbar.
Ein Browser initiiert die Hardwarebeschleunigung folgendermaßen: Werden z. B. 3D-Transformationen auf ein Element angewendet, so wird dieses Element in einer eigenen Ebene gekapselt und unabhängig vom Rest der Seite gerendert und repainted. Das spart zwar Rechenleistung, wird aber für die meisten Operationen von der hauseigenen Browser-Software übernommen. Diese Rendering-Engines sind langsamer als die GPU.

Wie bereits oben erwähnt, ist ein moderner Browser in der Lage, die anfallenden Rechenoperationen für bestimmte, wenige CSS-Eigenschaften selbständig der GPU zu übergeben. Will der Frontendler aber andere Eigenschaften über die GPU berechnen lassen, so bedarf es bis dato eines Hacks mit den wohlklingenden Namen Layer-Creation-Hack oder Null-transform-Hack.

  1. transform: translate3d(0, 0, 0);

Mit translate3d wird eine 3D-Transformation für das Element im CSS erzwungen und die Berechnung somit der GPU übergeben. Das hat aber eigentlich nichts mit der nativen Funktionsweise dieser Eigenschaft zu tun und ist deshalb lediglich ein Hack.

Auch in diesem Fall wird das betroffene Element in einen eigens dafür erstellten Layer übergeben, damit die Berechnung und das darauffolgende Compositing unabhängig von der Seite und somit ressourcensparender durchgeführt werden kann.

Schaubild Compositor Layer
Das Prinzip des Compositor Layers: 1) Ein Element auf der Seite wird zur Berechnung an die GPU übergeben. 2) In vielen modernen Browsern wird für bestimmte CSS-Properties (z.B. opacity, translate) ein eigener Layer erstellt, um die Veränderungen unabhängig vom Rest der Seite zu berechnen. Hier findet das Painting statt, dazu im Kapitel »Ein Test mit will-change« mehr. 3) Danach wird das Element wieder in die Seite eingefügt (Compositing). Auf diese Weise musste nur der Teilbereich bearbeitet werden und nicht die komplette Seite.

Die Erzeugung eines sogenannten Compositor Layers bedeutet in jedem Fall einen enormen initialen Rechenaufwand, der kurz vor der angestrebten Veränderung des Elements bewältigt werden muss. Diese Rechenleistung kann den Start einer Animation merklich verzögern.

2014: Performancekontrolle mit will-change

Tab Atkins Jr. von Google Inc. hat am 29. April 2014 den W3C Working Draft CSS Will Change Module Level 1 verfasst, der die neue CSS-Eigenschaft will-change beschreibt. Diese soll dem Browser ankündigen, dass sich ein Element z. B. über eine Animation in Zukunft verändern wird und dass für die Berechnung die benötigten Ressourcen eingeplant werden sollen. Als Richtwert für den Zeitraum zwischen Ankündigung und Durchführung gelten ca. 200 Millisekunden. Auf diese Weise kann z.B. eine Latenz vor dem Start einer Animation dann wiederum merklich verkürzt werden, weil der Browser bereits einige Arbeiten im Vorfeld erledigt hat.

  1. will-change: auto | scroll-position | contents | <custom-ident>

Laut der im Working Draft beschriebenen Spezifikation kann die will-change-Eigenschaft folgende Werte annehmen.

auto
auto ist der Ausgangswert. Ist der Wert von will-change explizit auf auto gesetzt, so wird veranlasst, dass der Browser dem Element keine besondere Beachtung zukommen lässt. Mit auto werden alle Werte zurückgesetzt.
scroll-position
Einige Browser neigen dazu, nur die sichtbaren Inhalte des Scroll-Fensters zu rendern, um Performance zu sparen. Mit scroll-position wird dem Browser angezeigt, dass sich die Position des Scroll-Fensters auf der Seite ändern wird und der Browser die nicht sichtbaren Bereiche nach und ggf. vor dem Fenster-Bereich vorrendern soll. Durch die daraus resultierende Verkürzung der anfallenden Berechnungen während des Scrollvorgangs wird ein schnelleres und flüssigeres Scrolling ermöglicht.
contents
Der Browser wird veranlasst, die Inhalte des Elements nicht oder nur beschränkt zu cachen, weil sich diese ändern werden. Laut Spezifikation können mit diesem Wert JavaScript-Animationen beschleunigt werden, da diese bestimmte Eigenschaften und Werte eines Elements mehrere Male pro Sekunde ändern können.
<custom-ident>
Custom-idents sind spezifische CSS-Eigenschaften, die animiert oder geändert werden sollen. Dazu zählen z.B. left, right, top, bottom, background (oder background-image) oder opacity. Es können theoretisch alle beliebigen Eigenschaften hier eingetragen werden, denn schlimmstenfalls werden sie einfach ignoriert.

Eigenschaften, die einen Stacking-Context für ihr Element erzeugen würden, erledigen dies bereits zum Zeitpunkt der Ankündigung durch will-change und zwar unabhängig von ihren Werten. Ein gutes Beispiel dafür ist Opacity. Diese Eigenschaft erzeugt einen Stacking-Context, wenn sie einen anderen Wert annimmt als eins. Weil das Element mit der Eigenschaft Opacity seine Transparenz unabhängig vom Rest der Seite erhält, wird es in einen eigenen Layer gepackt und mit Hilfe des z-index innerhalb des Elternelements gestapelt.

will-change in der Praxis

Beim Einsatz von will-change gibt es ein paar wichtige Dinge zu beachten. Zunächst darf die neue Eigenschaft nicht inflationär vergeben werden. Nur Elemente, die auch wirklich eine performancelastige Berechnung durchlaufen werden, kommen dafür in Frage, nicht prophylaktisch alle Elemente der Seite. Das würde zwangsläufig zu einem Performancecrash führen.

Das heißt aber wiederum auch, dass für Elemente, die den Compositor-Layer-Zyklus durchlaufen haben, die Werte für will-change wieder zurückgesetzt werden müssen, also auf will-change: auto;. Dieser Umstand zusammen mit der Tatsache, dass der Hinweis vor einer Veränderung des Elements stattfinden muss, suggeriert dem Entwickler bereits, dass er in vielen Fällen ohne eine gute Strategie für die Implementierung nicht allzu weit kommen wird.

Aber vor dem Klick erfolgt ja zum Glück auf den Nicht-Touch-Devices das Hover und dieses gibt dem Browser bereits genug Latenz, um die Vorbereitung auf die darauffolgende Veränderung einzuplanen. Das könnte dann so aussehen:

CSS

  1. .slide-wrapper li {
  2.   position: relative;
  3.   left: 0;
  4.   will-change: auto;
  5. }
  6. .slide-wrapper:hover li {
  7.  will-change: left;
  8. }
  9. .slide-wrapper li.bv-active {
  10.   left: -200px;
  11.   transition: left 0.6s;
  12. }

jQuery

  1. var slide = $(‘.slide-wrapper’),
  2.   animateTarget = slide.find(‘li’);
  3. slide.on(‘mouseenter touchstart’, function() {
  4.   animateTarget.addClass(‘bv-will-change’);
  5.     }).on(‘mouseleave touchend’, function() {
  6.       animateTarget.removeClass(‘bv-will-change’);
  7. });

JS

  1. var slide = document.querySelector(‘.slide-wrapper’),
  2.   animateTarget = document.querySelector(‘.slide-wrapper-li’);
  3. slide.addEventListener(‘mouseenter’, setWillChange);
  4. slide.addEventListener(‘animationEnd’, removeWillChange);
  5. function setWillChange () {
  6.   this.style.willChange = ‘left’;
  7. }
  8. function removeWillChange () {
  9.   this.style.willChange = ‘auto’;
  10. }

Auf den Touch-Devices, die bekanntlich keinen Hover-Zustand haben, wäre eine Möglicheit, will-change auf das Touchstart-Event zu vergeben. Dadurch kann die native Latenz von 300ms, die das Device verwendet, um zwischen einem Klick und einem Touch oder Swipe zu unterscheiden, für den zeitlichen Vorlauf für will-change genutzt werden.
Dem Entwickler obliegt es, hier eine funktionierende Strategie zu entwickeln.

Soviel zu den Klicks. Wenn aber eine Animation auf einen Hover erfolgen soll, so kann will-change z.B. über einen Hover auf ein Eltern-Element gesetzt werden.

Ein Beispiel, um die Anwendung von will-change in einem realen Projekt mal dauerhaft zu testen, ist der Content-Slider auf www.menadwork.com. Hier wird ähnlich den obigen Beispielen das will-change mittels einer Klasse über das Mouseenter auf das Elternelement sowie den beiden Slider-Control-Buttons übergeben.

Ein Test mit will-change

Ein einfacher Testcase verdeutlicht die Arbeitsweise von will-change und dem Compositor Layer. Der Testcase besteht aus 20 div-Containern mit ein paar rechenaufwändigen CSS-Formatierungen wie z.B. box-shadow oder border-radius. Die Container werden mittels der Positionierungen top und right und jQuery-Animate über den Bildschirm bewegt. Animate deswegen, damit die Hardwarebeschleunigung nicht schon standardmäßig aktiviert wird.

In den nachfolgenden Screenshots befindet sich die Variante mit will-change auf der linken Seite. In den DevTools des Chrome Browsers lässt sich der Unterschied im Direktvergleich hervorragend nachvollziehen. Es wurden dort allerdings nur 13 der 20 Elemente gepaintet, da sich beim initialen Ladevorgang nur 13 im Viewport befanden.

Paint vor dem Compositor Layer, Rendering Zeit kürzer
Performance-Vergleich des Paint-Events vor der Erstellung der Compositor Layer

Zunächst wird die komplette Seite in beiden Fällen initial auf den Bildschirm gemalt (Paint 1018 x 1018, das entspricht der Viewportgröße zum Zeitpunkt des Tests). In der Detailansicht im unteren Bereich ist deutlich zu erkennen, dass der Zeitaufwand für die Aktion noch ungefähr gleich hoch ist.

Paint vor dem Compositor Layer, Renderingzeit gleich
Performance-Vergleich des Paint-Events nach der Erstellung der Compositor Layer

Nachdem sich die einzelnen Elemente mit will-change in ihrem eigenen Layer befinden, geht der Zeitaufwand für Paint 1018 x 1018 deutlich auseinander. Während im Beispiel ohne will-change (rechts) das Painting der gesamten Seite für jeden einzelnen Abschnitt der Animation gestemmt werden muss, werden mit will-change diese Berechnungen nur auf die Elemente und deren Layer umgelegt, unabhängig vom Rest der Seite. Erst zum Schluss muss dann alles wieder auf dem Bildschirm zusammengesetzt werden (hier das Event Composite Layers).

Composite Layer Border
Die Composite Layer und ihre Elemente

Auf diesem Screenshot sind deutlich die Compositor Layer zu sehen. Die einzelnen Elemente in ihrem eigenen Layer werden auf dem Bildschirm (rechte Seite) mit einem gelben Rahmen gekennzeichnet.

Fazit

Die W3C-Spezifikation zu will-change befindet sich wie bereits erwähnt bis dato noch im Status »Working Draft«. Die neue Eigenschaft ist im Opera ab Version 24 sowie in den Chrome-Browsern ab Version 36 standardmäßig implementiert, im Firefox zwar bereits implementiert, aber nicht aktiviert, und im IE immerhin schon »Under Consideration«. Vgl. CSS will-change property auf Can I Use.

will-change ist die erste CSS-Eigenschaft, die dem Entwickler direkten Einfluss auf die Performance des Browsers gibt. Und sie kann den Null-Transform-Hack auf elegante und semantische Art ablösen. Allerdings stellt sich hier die grundlegende Frage, ob es in den Aufgabenbereich des Entwicklers fällt zu entscheiden, ob Maßnahmen zur Performance-Optimierung angewendet werden – zumal die strategische Implementierung mit einigem Aufwand verbunden ist – oder der Browser eher selbständig erkennen sollte, wann und wie optimiert werden kann. Schließlich werden dort die Ressourcen verwaltet. Und vielleicht ist es ja auch nur eine Frage der Zeit, bis die Browser in der Lage sind, Performsanceoptimierungen selbständig in die Wege zu leiten und will-change dann wieder in Vergessenheit gerät.
Aber bis dahin kann noch viel Wasser den Rhein herunter fließen.

Weiterführende Links

comments powered by Disqus