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, die Community der Webentwickler, nur die Magie miterleben. 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 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 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.

Spezifikationen

Worklets (spec)

Worklets sind für sich genommen nicht wirklich nützlich. Sie wurden eingeführt, um viele der späteren Entwürfe umzusetzen. Wenn Sie beim Lesen von „Worklet“ an Web Worker gedacht haben, liegen Sie nicht falsch. Sie überschneiden sich in vielerlei Hinsicht. Warum also etwas Neues, wenn wir bereits Mitarbeiter haben?

Houdini möchte neue APIs bereitstellen, damit Webentwickler ihren eigenen Code in die CSS-Engine und die umliegenden Systeme einbinden 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 Web Worker für die von Houdini geplanten Aufgaben nicht praktikabel 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. Lesen Sie die ausführliche Einführung.

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). Als letzten Schritt werden alle diese einzelnen, jetzt gemalten Ebenen gestapelt und übereinander positioniert, wobei Z-Indizes, 3D-Transformationen usw. 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 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 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. 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 außergewöhnliche Dinge mit beeindruckenden 60 fps erreichen.

Hier sehen Sie eine vollständige Implementierung für das Parallaxen-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 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 dafür praktisch leer, aber das Konzept ist faszinierend: Schreiben Sie 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 Überlauf. 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 (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 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. Das ist vorbei! und CSS steht kurz davor, etwas zu 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, percent usw. 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}

Attribute 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

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 passiert, wenn ich Ruby-Anmerkungen verwende? Diese Wünsche wurden oft gewünscht und Houdini sollte diese Wünsche endlich erfüllen können.

Halt – 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 neue Möglichkeiten bieten.

Demos

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