Houdini – CSS entmystifizieren

Haben Sie sich schon einmal gefragt, wie viel Arbeit CSS für Sie erledigt? Sie ändern ein einzelnes Attribut und plötzlich wird Ihre gesamte Website in einem anderen Layout angezeigt. Es ist magisch. Bisher konnten wir – die Community der Webentwickler – nur zusehen und beobachten, wie die Magie funktioniert. Was, wenn wir uns unsere eigene Magie ausdenken wollen? Was, wenn wir selbst zum Magier werden möchten?

Houdini

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

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

Die Spezifikationen

Worklets (spec)

Worklets sind an sich nicht wirklich nützlich. Sie wurden eingeführt, um viele der späteren Entwürfe zu ermöglichen. Wenn Sie beim Lesen von „Worklet“ an Web Workers gedacht haben, liegen Sie nicht falsch. Sie haben viele konzeptionelle Überschneidungen. Warum also etwas Neues, wenn wir bereits Worker haben?

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

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

CSS Paint API (Spezifikation)

Die Paint API ist in Chrome 65 standardmäßig aktiviert. Ausführliche Einführung

Compositor-Worklet

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

Obwohl die Compositor-Worklet-Spezifikation in die 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. Das hängt jedoch sowohl von Ihrer Grafikkarte als auch von Ihrem Gerät ab.

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

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

Compositor-Worklet

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

Genauer gesagt, können Sie dem Browser mitteilen, dass Sie sich in den Compositing-Prozess 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 verschoben und Ihr Code wird bei jedem Frame aufgerufen. Sie können die Ebene verschieben, indem Sie ihre Transformation bearbeiten und ihre Attribute (z. B. opacity) ändern. So lassen sich beeindruckende Effekte mit 60 fps erzielen.

Hier sehen Sie eine vollständige Implementierung für das Parallax-Scrolling 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, das Sie ausprobieren können. Die Leistung wird dadurch allerdings deutlich beeinträchtigt.

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 faszinierend: Schreiben Sie Ihr eigenes Layout! Mit dem Layout-Worklet können Sie display: layout('myLayout') und JavaScript ausführen, um die untergeordneten Elemente eines Knotens im Feld des Knotens anzuordnen.

Eine vollständige JavaScript-Implementierung des flex-box-Layouts von CSS ist natürlich langsamer als eine entsprechende native Implementierung. Es ist jedoch leicht vorstellbar, dass durch das Abkürzen von Ecken und Kanten ein Leistungszuwachs erzielt werden kann. Stellen Sie sich eine Website vor, die nur aus Kacheln besteht, wie Windows 10 oder ein Layout im Mauerwerkstil. Es wird keine absolute oder feste Positionierung verwendet, ebenso wenig wie z-index. Außerdem überlappen sich Elemente nie und haben auch keine Art von Rahmen oder Überlauf. Wenn Sie alle diese Prüfungen beim erneuten Layout überspringen können, kann dies zu einer Leistungssteigerung führen.

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 Typed CSSOM (CSS Object Model oder Cascading Style Sheets Object Model) löst ein Problem, das wir wahrscheinlich alle schon einmal hatten und mit dem wir uns abgefunden haben. Hier ein Beispiel mit einer JavaScript-Zeile:

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

Wir führen Berechnungen durch, wandeln eine Zahl in einen String um, um eine Einheit anzuhängen, nur damit der Browser diesen String parsen und für die CSS-Engine wieder in eine Zahl umwandeln kann. Das wird noch komplizierter, wenn Sie Transformationen mit JavaScript bearbeiten. Das ist nicht mehr nötig. CSS wird bald typisiert.

Dieser Entwurf ist einer der ausgereifteren und es wird bereits an einem Polyfill gearbeitet. (Hinweis: Die Verwendung des Polyfills führt natürlich zu noch mehr Rechenaufwand. Es soll nur gezeigt werden, wie praktisch die API ist.)

Statt mit Strings arbeiten Sie mit dem 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 Dictionary aller CSS-Einheiten wie em, rem, px, percent usw. Wenn Sie height: calc(5px + 5%) festlegen, wird LengthValue{px: 5, percent: 5} zurückgegeben. Für einige Properties wie box-sizing sind nur bestimmte Keywords zulässig. Daher haben sie den Wertetyp KeywordValue. Die Gültigkeit dieser Attribute kann dann zur Laufzeit geprü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}

Attribute und Werte

(Spezifikation)

Kennen Sie benutzerdefinierte CSS-Properties (oder deren inoffiziellen Alias „CSS-Variablen“)? Das sind sie, aber mit Typen. Bisher konnten Variablen nur Stringwerte haben und es wurde ein einfacher Ansatz zum Suchen und Ersetzen verwendet. Mit diesem Entwurf können Sie nicht nur einen Typ für Ihre Variablen angeben, sondern auch einen Standardwert definieren und das Verhalten der Vererbung über eine JavaScript API beeinflussen. Technisch gesehen könnten damit auch benutzerdefinierte Eigenschaften mit standardmäßigen CSS-Übergängen und ‑Animationen animiert werden. Das wird ebenfalls in Betracht gezogen.

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

Schriftartmesswerte

Schriftartmesswerte sind genau das, wonach es sich anhört. Wie lautet der Begrenzungsrahmen (oder die Begrenzungsrahmen), wenn ich String X mit Schriftart Y in Größe Z rendere? Was passiert, wenn ich Rubin-Anmerkungen verwende? Viele Nutzer haben sich das gewünscht und Houdini soll diese Wünsche nun endlich erfüllen.

Halt – das war noch nicht alles!

In der Liste der Entwürfe von Houdini gibt es noch mehr Spezifikationen, aber die Zukunft dieser Entwürfe ist eher ungewiss und sie sind nicht viel mehr als Platzhalter für Ideen. Beispiele hierfür sind benutzerdefinierte Überlaufverhalten, die CSS-Syntaxerweiterungs-API, die Erweiterung des nativen Scrollverhaltens und ähnlich anspruchsvolle Dinge, die auf der Webplattform bisher nicht mögliche Dinge ermöglichen.

Demos

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