Houdini – CSS entmystifizieren

Haben Sie schon einmal darüber nachgedacht, wie viel Arbeit CSS macht? Wenn Sie ein einzelnes Attribut ändern, wird plötzlich Ihre gesamte Website in einem anderen Layout angezeigt. Das ist eine Art Magie. Bisher konnten wir, die Community der Webentwickler, nur die Magie miterleben. Wie wäre es, wenn wir uns unsere eigene Magie ausdenken wollen? Was ist, wenn wir Magier werden wollen?

Also, Houdini!

Die Houdini-Taskforce besteht aus Ingenieuren von Mozilla, Apple, Opera, Microsoft, HP, Intel und Google, die gemeinsam Webentwicklern bestimmte Teile der CSS-Engine zur Verfügung stellen. Die Taskforce arbeitet an einer Sammlung von Entwürfen mit dem Ziel, diese vom W3C als echte Webstandards akzeptieren zu lassen. Sie setzten sich einige übergeordnete Ziele und verwandelten sie in Spezifikationsentwürfe, aus denen wiederum eine Reihe unterstützender, untergeordneter Spezifikationsentwürfe entstanden sind.

Die Sammlung dieser Entwürfe ist normalerweise gemeint, wenn jemand über „Houdini“ spricht. Zum Zeitpunkt der Erstellung dieses Dokuments war die Liste der Entwürfe unvollständig und einige der Entwürfe sind lediglich Platzhalter.

Spezifikationen

Worklets (spec)

Worklets an sich sind nicht sehr nützlich. Sie sind ein Konzept, mit dem viele der späteren Entwürfe realisiert werden sollen. Wenn Sie beim Lesen von „Worklet“ an Web Worker gedacht haben, liegen Sie nicht falsch. Es gibt viele konzeptionelle Überschneidungen. Warum also etwas Neues, wenn wir bereits Worker haben?

Houdini möchte neue APIs bereitstellen, damit Webentwickler ihren eigenen Code in die CSS-Engine und die umliegenden Systeme einbinden können. Die Annahme, dass einige dieser Codefragmente jeden einzelnen Frame ausführen müssen, ist vermutlich nicht unrealistisch. Einige müssen das per Definition tun. Zitieren Sie die Web Worker-Spezifikation:

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

CSS Paint API (spec)

Die Paint API ist in Chrome 65 standardmäßig aktiviert. Lesen Sie die ausführliche Einführung.

Compositor-Worklet

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

Obwohl die Compositor-Worklet-Spezifikation in die WICG verschoben wurde und iteriert wird, gefällt mir diese Spezifikation am meisten. Einige Vorgänge werden von der CSS-Engine für die Grafikkarte Ihres Computers ausgelagert. Dies hängt jedoch sowohl von Ihrer Grafikkarte als auch von Ihrem Gerät im Allgemeinen ab.

Ein Browser verwendet normalerweise den DOM-Baum und entscheidet anhand bestimmter Kriterien, einigen Zweigen und Unterstrukturen eine eigene Ebene zuzuweisen. Diese Unterstrukturen zeichnen sich selbst auf (vielleicht wird in Zukunft ein Paint-Worklet verwendet). Als letzten Schritt werden all diese einzelnen, jetzt dargestellten Ebenen, unter Berücksichtigung von Z-Indizes, 3D-Transformationen usw. übereinander gestapelt und positioniert, um das endgültige Bild zu erhalten, das auf Ihrem Bildschirm sichtbar ist. Dieser Vorgang wird als Compositing bezeichnet und vom Compositor ausgeführt.

Der Vorteil des Erstellungsprozesses besteht darin, dass Sie nicht alle Elemente selbst aktualisieren müssen, wenn auf der Seite ein kleines Bild gescrollt wird. Stattdessen können Sie die Layer aus dem vorherigen Frame wiederverwenden und den Compositor mit der aktualisierten Scrollposition noch einmal ausführen. Das macht alles schnell. Dadurch erreichen wir 60 fps.

Compositor-Worklet.

Wie der Name schon sagt, können Sie sich mit dem Compositor-Worklet in den Compositor einhängen und beeinflussen, wie die Ebene eines Elements, die bereits dargestellt wurde, positioniert und über die anderen Ebenen gelegt wird.

Wenn Sie etwas spezifischer werden möchten, können Sie dem Browser mitteilen, dass Sie den Zusammensetzungsprozess für einen bestimmten DOM-Knoten einbinden möchten und Zugriff auf bestimmte Attribute wie Scrollposition, transform oder opacity anfordern können. Dadurch wird dieses Element auf seine eigene Ebene erzwungen und in jedem Frame wird Ihr Code aufgerufen. Sie können Ihre Ebene verschieben, indem Sie die Transformation der Ebenen bearbeiten und ihre Attribute (z. B. opacity) ändern. Dadurch können Sie raffinierte und detailgetreue Dinge mit beeindruckenden 60 fps erreichen.

Hier sehen Sie eine vollständige Implementierung für das Parallaxe-Scrollen 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 deutlich stärkeren Auswirkungen auf die Leistung.

Layout-Worklet (spec)

Der erste echte Spezifikationsentwurf wurde vorgeschlagen. Die Implementierung ist aber schon bald abgeschlossen.

Auch hier ist die Spezifikation dafür praktisch leer, aber das Konzept ist faszinierend: Schreiben Sie Ihr eigenes Layout! Das Layout-Worklet soll es Ihnen ermöglichen, display: layout('myLayout') auszuführen und Ihren JavaScript-Code auszufü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 eine gleichwertige native Implementierung. Sie können sich jedoch ein Szenario vorstellen, bei dem das Verkleinern von Ecken zu einer Leistungssteigerung führen kann. Stellen Sie sich eine Website vor, die ausschließlich aus Kacheln besteht, wie zum Beispiel Windows 10 oder ein Layout im Maurerstil. Absolute und feste Positionierung werden weder verwendet, noch z-index. Elemente überlappen sich nicht und weisen einen Rahmen oder Überlauf auf. Die Möglichkeit, alle Überprüfungen des Layout-Neulayouts zu überspringen, könnte 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 (spec)

Das Typ „CSSOM (CSS Object Model)“ oder „Cascading Style Sheets Object Model“ (CSS-Objektmodell oder Cascading Style Sheets Object Model) behebt ein Problem, auf das wir wahrscheinlich alle gerade gestoßen sind. Im Folgenden wird das mit einer Zeile von JavaScript veranschaulicht:

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

Wir rechnen eine Zahl an und wandeln eine Zahl in einen String um, um eine Einheit hinzuzufügen, damit der Browser diesen String parst und wieder in eine Zahl für die CSS-Engine konvertiert. Dies wird noch größer, wenn Sie Transformationen mit JavaScript bearbeiten. Nichts mehr! und CSS steht kurz davor, etwas zu tippen.

Dieser Entwurf ist einer der ausgereifteren und an einem polyfill wird bereits gearbeitet. (Haftungsausschluss: Die Verwendung des Polyfills führt zu einem noch mehr Rechenaufwand. Wichtig ist, zu zeigen, wie praktisch die API ist.)

Anstelle von Strings arbeiten Sie nun am StylePropertyMap eines Elements, wobei jedes CSS-Attribut einen eigenen Schlüssel und einen entsprechenden Werttyp hat. Für Attribute wie width ist LengthValue als Werttyp festgelegt. Ein LengthValue ist ein Wörterbuch aller CSS-Einheiten wie em, rem, px, percent usw. Das Festlegen von height: calc(5px + 5%) würde zu einem LengthValue{px: 5, percent: 5} führen. Einige Attribute wie box-sizing akzeptieren nur bestimmte Keywords und haben daher den Werttyp KeywordValue. Die Gültigkeit dieser Attribute kann dann während der 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

(spec)

Kennen Sie benutzerdefinierte CSS-Eigenschaften (oder deren inoffizielles Alias "CSS-Variablen")? Das sind sie, nur mit Typen! Bisher war für Variablen nur Stringwerte möglich, für die ein einfaches Suchen-und-Ersetzen-Ansatz verwendet wurde. Mit diesem Entwurf können Sie nicht nur einen Typ für Ihre Variablen angeben, sondern auch einen Standardwert definieren und das Übernahmeverhalten mithilfe einer JavaScript API beeinflussen. Technisch gesehen ermöglicht dies auch, dass benutzerdefinierte Eigenschaften mit standardmäßigen CSS-Übergängen und -Animationen animiert werden, was ebenfalls berücksichtigt wird.

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

Messwerte für Schriftarten

Schriftartmesswerte sind genau das, wonach es sich anhört. Was ist der Begrenzungsrahmen (oder die Begrenzungsrahmen), wenn ich die Zeichenfolge X mit der Schriftart Y und der Größe Z rendere? Was ist, wenn ich Ruby-Annotationen verwende? Diese Wünsche wurden oft gewünscht und Houdini sollte diese Wünsche endlich erfüllen können.

Das war noch nicht alles!

Die Entwürfe von Houdini enthalten noch mehr Spezifikationen, aber deren Zukunft ist eher ungewiss und sie sind nicht viel mehr als Platzhalter für Ideen. Beispiele hierfür sind benutzerdefiniertes Überlaufverhalten, die API für die CSS-Syntaxerweiterung, die Erweiterung des nativen Scrollverhaltens und ebenso ambitionierte Dinge, die auf der Webplattform Möglichkeiten bieten, die zuvor nicht möglich waren.

Demos

Ich habe den Code für die Demo als Open Source zur Verfügung gestellt (Live-Demo mit Polyfill).