Houdini – CSS entmystifizieren

Haben Sie sich schon einmal gefragt, wie viel Arbeit CSS leistet? Sie ändern ein einzelnes Attribut und plötzlich wird Ihre gesamte Website in einem anderen Layout angezeigt. Es ist magisch. Bisher konnten wir als Webentwickler nur zusehen und beobachten. Was ist, wenn wir unsere eigene Magie erfinden möchten? Was, wenn wir Zauberer werden möchten?

Willkommen bei Houdini!

Die Houdini-Taskforce besteht aus Entwicklern von Mozilla, Apple, Opera, Microsoft, HP, Intel und Google, die gemeinsam daran arbeiten, bestimmte Teile der CSS-Engine für Webentwickler zugänglich zu machen. Die Taskforce arbeitet an einer Sammlung von Entwürfen, die vom W3C akzeptiert werden sollen, um zu echten Webstandards zu werden. Sie haben sich einige übergeordnete Ziele gesetzt, diese in Spezifikationsentwürfe umgewandelt und daraus wiederum eine Reihe von unterstützenden Spezifikationsentwürfen auf niedrigerer Ebene entwickelt.

Die Sammlung dieser Entwürfe wird in der Regel als „Houdini“ bezeichnet. Zum Zeitpunkt der Erstellung dieses Artikels ist die Liste der Entwürfe unvollständig und einige der Entwürfe sind lediglich Platzhalter.

Die Spezifikationen

Worklets (spec)

Worklets sind für sich genommen nicht wirklich nützlich. Sie sind ein Konzept, das eingeführt wurde, um viele der späteren Entwürfe zu ermöglichen. Wenn Sie bei „Worklet“ an Webworker gedacht haben, liegen Sie nicht falsch. Sie überschneiden sich konzeptionell stark. Warum also etwas Neues, wenn wir bereits Mitarbeiter haben?

Ziel von Houdini ist es, neue APIs bereitzustellen, mit denen Webentwickler ihren eigenen Code an die CSS-Engine und die zugehörigen Systeme anbinden können. Es ist wahrscheinlich nicht unrealistisch anzunehmen, dass einige dieser Codefragmente in jedem Frame ausgeführt werden müssen. Einige davon sind per Definition erforderlich. Zitat aus der Web Worker-Spezifikation:

Das bedeutet, dass Webworker für die Aufgaben, die Houdini ausführen soll, nicht geeignet sind. Daher wurden Worklets erfunden. In Worklets werden ES2015-Klassen verwendet, um eine Sammlung von Methoden zu definieren, deren Signaturen vom Typ des Worklets vordefiniert sind. Sie sind leicht und kurzlebig.

CSS Paint API (Spezifikation)

Die Paint API ist in Chrome 65 standardmäßig aktiviert. Weitere Informationen

Composer-Worklet

Die hier beschriebene API ist veraltet. Das Compositor-Worklet wurde neu gestaltet und wird jetzt als „Animation Worklet“ bezeichnet. Weitere Informationen zur aktuellen Version der API

Auch wenn die Spezifikation für das Compositor-Worklet zum WICG verschoben wurde und weiterentwickelt wird, ist sie die Spezifikation, die mich am meisten begeistert. Einige Vorgänge werden von der CSS-Engine an die Grafikkarte Ihres Computers ausgelagert. Dies hängt jedoch sowohl von Ihrer Grafikkarte als auch von Ihrem Gerät im Allgemeinen ab.

Ein Browser nimmt in der Regel den DOM-Baum und entscheidet anhand bestimmter Kriterien, einigen Ästen und untergeordneten Bäumen eine eigene Ebene zuzuweisen. Diese untergeordneten Bäume werden automatisch darauf gezeichnet (möglicherweise in Zukunft mit einem Paint-Worklet). Im letzten Schritt werden alle diese einzelnen, jetzt gemalten Ebenen übereinander gestapelt und positioniert, wobei Z-Indizes und 3D-Transformationen berücksichtigt werden, um das endgültige Bild zu erhalten, das auf dem Bildschirm sichtbar ist. Dieser Vorgang wird als Compositing bezeichnet und vom Compositor ausgeführt.

Der Vorteil des Compositing-Prozesses besteht darin, dass nicht alle Elemente neu gemalt werden müssen, wenn die Seite ein wenig gescrollt wird. Stattdessen können Sie die Ebenen aus dem vorherigen Frame wiederverwenden und den Renderer einfach mit der aktualisierten Scrollposition noch einmal ausführen. Das geht schnell. So können wir 60 fps erreichen.

Compositor-Worklet

Wie der Name schon sagt, können Sie mit dem Compositor-Worklet den Compositor einbinden und beeinflussen, wie die bereits gemalte Ebene eines Elements positioniert und über die anderen Ebenen gelegt wird.

Genauer gesagt: Sie können dem Browser mitteilen, dass Sie sich in den Kompositionsprozess für einen bestimmten DOM-Knoten einklinken möchten, und Zugriff auf bestimmte Attribute wie die Scrollposition, transform oder opacity anfordern. Dadurch wird dieses Element auf eine eigene Ebene gesetzt und Ihr Code wird in jedem Frame aufgerufen. Du kannst die Ebene durch Ändern der Ebenentransformation verschieben und ihre Attribute (z. B. opacity) ändern, um coole Sachen mit satten 60 fps zu machen.

Hier sehen Sie eine vollständige Implementierung des Parallaxe-Scrollings mit dem Compositor-Worklet.

// main.js
window.compositorWorklet.import('worklet.js')
    .then(function() {
    var animator = new CompositorAnimator('parallax');
    animator.postMessage([
        new CompositorProxy($('.scroller'), ['scrollTop']),
        new CompositorProxy($('.parallax'), ['transform']),
    ]);
    });

// worklet.js
registerCompositorAnimator('parallax', class {
    tick(timestamp) {
    var t = self.parallax.transform;
    t.m42 = -0.1 * self.scroller.scrollTop;
    self.parallax.transform = t;
    }

    onmessage(e) {
    self.scroller = e.data[0];
    self.parallax = e.data[1];
    };
});

Robert Flack hat ein Polyfill für das Compositor-Worklet geschrieben, damit Sie es ausprobieren können – natürlich mit einer deutlich höheren Leistungsauswirkung.

Layout-Worklet (Spezifikation)

Der erste echte Spezifikationsentwurf wurde vorgeschlagen. Die Implementierung ist noch in weiter Ferne.

Auch hier ist die Spezifikation praktisch leer, aber das Konzept ist interessant: Sie schreiben Ihr eigenes Layout! Mit dem Layout-Worklet können Sie display: layout('myLayout') ausführen und Ihr JavaScript ausführen, um die untergeordneten Elemente eines Knotens im Feld des Knotens anzuordnen.

Natürlich ist die Ausführung einer vollständigen JavaScript-Implementierung des flex-box-Layouts von CSS langsamer als die Ausführung einer entsprechenden nativen Implementierung. Es ist jedoch leicht vorstellbar, dass sich durch das Weglassen von Details eine Leistungssteigerung erzielen lässt. Stellen Sie sich eine Website vor, die nur aus Kacheln besteht, wie Windows 10 oder ein Layout im Mauerwerksstil. Absolute und feste Positionierung werden nicht verwendet, ebenso wenig wie z-index. Elemente überschneiden sich auch nicht und haben keine Ränder oder Überläufe. Wenn Sie beim Neulayout all diese Prüfungen überspringen können, kann sich das positiv auf die Leistung auswirken.

registerLayout('random-layout', class {
    static get inputProperties() {
        return [];
    }
    static get childrenInputProperties() {
        return [];
    }
    layout(children, constraintSpace, styleMap) {
        const width = constraintSpace.width;
        const height = constraintSpace.height;
        for (let child of children) {
            const x = Math.random()*width;
            const y = Math.random()*height;
            const constraintSubSpace = new ConstraintSpace();
            constraintSubSpace.width = width-x;
            constraintSubSpace.height = height-y;
            const childFragment = child.doLayout(constraintSubSpace);
            childFragment.x = x;
            childFragment.y = y;
        }

        return {
            minContent: 0,
            maxContent: 0,
            width: width,
            height: height,
            fragments: [],
            unPositionedChildren: [],
            breakToken: null
        };
    }
});

Typisiertes CSSOM (Spezifikation)

Das typisierte CSSOM (CSS Object Model oder Cascading Style Sheets Object Model) behebt ein Problem, mit dem wir wahrscheinlich alle schon einmal konfrontiert wurden und das wir einfach hingenommen haben. Hier ein Beispiel mit einer JavaScript-Zeile:

    $('#someDiv').style.height = getRandomInt() + 'px';

Wir führen eine Berechnung durch, wandeln eine Zahl in einen String um, um eine Einheit anzuhängen, nur damit der Browser diesen String analysiert und wieder in eine Zahl für die CSS-Engine umwandelt. Das wird noch schlimmer, wenn Sie Transformationen mit JavaScript manipulieren. Das ist vorbei! Jetzt geht es ans Tippen.

Dieser Entwurf ist einer der ausgereifteren und es wird bereits an einer Polyfill gearbeitet. (Haftungsausschluss: Die Verwendung der Polyfills führt natürlich zu noch mehr Rechenaufwand. Es geht darum zu zeigen, wie praktisch die API ist.)

Anstatt mit Strings arbeiten Sie mit den StylePropertyMap eines Elements, wobei jedes CSS-Attribut einen eigenen Schlüssel und einen entsprechenden Werttyp hat. Attribute wie width haben LengthValue als Werttyp. Ein LengthValue ist ein Wörterbuch aller CSS-Einheiten wie em, rem, px und percent. Wenn Sie height: calc(5px + 5%) festlegen, ergibt sich LengthValue{px: 5, percent: 5}. Für einige Properties wie box-sizing sind nur bestimmte Keywords zulässig. Sie haben daher den Werttyp KeywordValue. Die Gültigkeit dieser Attribute kann dann zur Laufzeit überprüft werden.

<div style="width: 200px;" id="div1"></div>
<div style="width: 300px;" id="div2"></div>
<div id="div3"></div>
<div style="margin-left: calc(5em + 50%);" id="div4"></div>
var w1 = $('#div1').styleMap.get('width');
var w2 = $('#div2').styleMap.get('width');
$('#div3').styleMap.set('background-size',
    [new SimpleLength(200, 'px'), w1.add(w2)])
$('#div4')).styleMap.get('margin-left')
    // => {em: 5, percent: 50}

Eigenschaften und Werte

(spec)

Kennen Sie benutzerdefinierte CSS-Properties (oder ihren inoffiziellen Alias „CSS-Variablen“)? Hier sind sie, aber mit Typen! Bisher konnten Variablen nur Stringwerte haben und es wurde ein einfacher Such- und Ersetzungsansatz verwendet. Mit diesem Entwurf können Sie nicht nur einen Typ für Ihre Variablen angeben, sondern auch einen Standardwert definieren und das Vererbungsverhalten mithilfe einer JavaScript API beeinflussen. Technisch gesehen könnten so auch benutzerdefinierte Properties mit standardmäßigen CSS-Übergängen und -Animationen animiert werden. Diese Option wird derzeit geprüft.

["--scale-x", "--scale-y"].forEach(function(name) {
document.registerProperty({
    name: name,
    syntax: "<number>",
    inherits: false,
    initialValue: "1"
    });
});

Schriftmesswerte

Schriftmetriken sind genau das, was der Name vermuten lässt. Wie groß ist der Begrenzungsrahmen, wenn ich String X mit Schriftart Y in Größe Z rendere? Was passiert, wenn ich Ruby-Anmerkungen verwende? Diese Funktion wurde oft angefragt und Houdini sollte diese Wünsche endlich erfüllen.

Halt – das war noch nicht alles!

In der Liste der Entwürfe von Houdini gibt es noch mehr Spezifikationen, deren Zukunft jedoch eher ungewiss ist und die nicht viel mehr als Platzhalter für Ideen sind. Beispiele hierfür sind benutzerdefinierte Überlaufsverhalten, die CSS-Syntaxerweiterungs-API, die Erweiterung des nativen Scrollverhaltens und ähnliche ehrgeizige Dinge, die auf der Webplattform Dinge ermöglichen, die vorher nicht möglich waren.

Demos

Ich habe den Code für die Demo als Open Source veröffentlicht (Live-Demo mit polyfill).