Houdini – CSS entmystifizieren

Haben Sie jemals darüber nachgedacht, wie viel Arbeit CSS bedeutet? Sie ändern ein einzelnes Attribut und plötzlich erscheint Ihre gesamte Website in einem anderen Layout. Das ist eine Art Magie. Bisher konnten wir – die Community der Webentwickler – diese Magie nur miterleben und beobachten. Was ist, wenn wir uns eigene Magie ausdenken wollen? Was ist, wenn wir Zauberer werden wollen?

Hier kommt Houdini!

Die Houdini-Taskforce besteht aus Ingenieuren von Mozilla, Apple, Opera, Microsoft, HP, Intel und Google, die gemeinsam bestimmte Teile der CSS-Engine für Webentwickler zugänglich machen. Die Taskforce arbeitet an einer Sammlung von Entwürfen, damit diese vom W3C akzeptiert und zu echten Webstandards werden. Sie setzten sich einige übergeordnete Ziele und verwandelten sie in Spezifikationsentwürfe, die wiederum eine Reihe von unterstützenden, allgemeineren Spezifikationsentwürfen hervorbrachten.

Die Sammlung dieser Entwürfe ist normalerweise gemeint, wenn jemand über "Houdini" spricht. 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 an sich sind nicht wirklich nützlich. Sie sind ein Konzept, das eingeführt wurde, um viele der späteren Entwürfe möglich zu machen. Beim Lesen von "Worklet" haben Sie an Web Worker gedacht. Die Konzepte überschneiden sich häufig. Warum also etwas Neues, wenn wir bereits Mitarbeiter haben?

Das Ziel von Houdini ist es, neue APIs zur Verfügung zu stellen, mit denen Webentwickler ihren eigenen Code in die CSS-Engine und die umgebenden Systeme einbinden können. Es ist wahrscheinlich nicht unrealistisch anzunehmen, dass einige dieser Codefragmente every. single. frame ausgeführt werden müssen. Einige von ihnen müssen per Definition dies tun. Die Web Worker-Spezifikation wird zitiert:

Das bedeutet, dass Web Worker nicht für das geeignet sind, was Houdini plant. 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 (spec)

Die Paint API ist in Chrome 65 standardmäßig aktiviert. Detaillierte 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 Iteration der API

Obwohl die Compositor Worklet-Spezifikation in die WICG verschoben wurde und weiter iteriert wird, ist es die Spezifikation, die mich am meisten begeistert. Einige Vorgänge werden von der CSS-Engine auf die Grafikkarte Ihres Computers ausgelagert, obwohl dies allgemein sowohl von Ihrer Grafikkarte als auch von Ihrem Gerät abhängig ist.

Ein Browser verwendet normalerweise den DOM-Baum und beschließt anhand bestimmter Kriterien, einigen Zweigen und Unterstrukturen eine eigene Ebene zuzuweisen. Diese Unterstrukturen malen sich selbst auf (vielleicht mit einem Paint-Worklet). Als letzten Schritt werden all diese einzelnen nun gezeichneten Ebenen unter Berücksichtigung von Z-Indizes, 3D-Transformationen usw. übereinander gestapelt und positioniert, 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 Zusammensetzungsprozesses besteht darin, dass nicht für alle Elemente eine Aktualisierung erforderlich ist, wenn die Seite nur ein kleines Bit gescrollt wird. Stattdessen können Sie die Layers aus dem vorherigen Frame wiederverwenden und den Compositor mit der aktualisierten Scrollposition einfach noch einmal ausführen. Das macht die Sache noch schneller. Dadurch können wir 60 fps erreichen.

Compositor-Worklet.

Wie der Name schon sagt, können Sie sich mit dem Compositor-Worklet in den Compositor einhängen und beeinflussen, wie die bereits dargestellte Ebene eines Elements auf den anderen Layern positioniert und überlagert wird.

Sie können dem Browser auch mitteilen, dass Sie in den Zusammensetzungsprozess für einen bestimmten DOM-Knoten einsteigen möchten und Zugriff auf bestimmte Attribute wie die Scrollposition, transform oder opacity anfordern können. Dadurch wird dieses Element in einen eigenen Layer eingebunden und in jedem Frame wird Ihr Code aufgerufen. Sie können Ihre Ebene verschieben, indem Sie die Layers transformieren und ihre Attribute ändern (z. B. opacity) und so raffinierte Dinge mit beeindruckenden 60 fps schaffen.

Hier sehen Sie eine vollständige Implementierung für das Parllax-Scrollen mithilfe des Compositor-Worklets.

// 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 – natürlich mit deutlich höherer Leistung.

Layout-Worklet (spec)

Der erste Entwurf der echten Spezifikationen wurde vorgeschlagen. Die Implementierung ist noch eine Weile entfernt.

Auch hier ist die Spezifikation 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 JavaScript auszuführen, um die untergeordneten Elemente eines Knotens im Feld des Knotens anzuordnen.

Natürlich ist eine vollständige JavaScript-Implementierung des flex-box-Layouts von CSS langsamer als die Ausführung einer gleichwertigen nativen Implementierung. Man kann sich aber leicht ein Szenario vorstellen, in dem Kleinigkeiten zu einer Leistungssteigerung führen können. Stellen Sie sich eine Website vor, die nur aus Kacheln besteht, z. B. mit Windows 10 oder einem steinernen Layout. Es werden weder absolute und feste Positionen noch z-index verwendet. Außerdem überschneiden sich Elemente nicht, haben irgendeine Art von Rahmen oder Überlauf. Die Möglichkeit, alle diese Überprüfungen beim Re-Layout 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
        };
    }
});

Eingegebenes CSSOM (spec)

Das typisierte CSSOM (CSS Object Model oder Cascading Style Sheets Object Model) ist ein Problem, das wir wahrscheinlich alle schon kennengelernt haben und gerade erst gelernt haben. Lassen Sie mich das anhand einer JavaScript-Zeile veranschaulichen:

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

Wir führen Berechnungen durch: Wir wandeln eine Zahl in einen String um, um eine Einheit anzufügen, nur damit der Browser den String parst und für die CSS-Engine zurück in eine Zahl konvertiert. Noch härter wird es, wenn Sie Transformationen mit JavaScript bearbeiten. Nichts mehr! CSS ist kurz vor der Eingabe.

Dieser Entwurf ist einer der ausgereifteren. An einem polyfill wird bereits gearbeitet. (Haftungsausschluss: Die Verwendung von Polyfill erhöht natürlich noch mehr Rechenaufwand. Es geht darum, zu zeigen, wie praktisch die API ist.)

Anstelle von Strings arbeiten Sie am 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. Die Einstellung height: calc(5px + 5%) würde zu LengthValue{px: 5, percent: 5} führen. Einige Properties wie box-sizing akzeptieren nur bestimmte Keywords und 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 Eigenschaften von CSS (oder ihren inoffiziellen Alias „CSS-Variablen“)? Das sind sie, aber mit Typen! Bisher konnten Variablen nur Stringwerte enthalten und es wurde ein einfacher Suchen-und-Ersetzen-Ansatz verwendet. Mit diesem Entwurf könnten 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 könnte dies auch die Animation benutzerdefinierter Eigenschaften mit Standard-CSS-Übergängen und -Animationen ermöglichen, was ebenfalls in Betracht gezogen 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. Wie groß sind die Begrenzungsrahmen, wenn ich den String X mit Schrift Y in der Größe Z rendere? Was ist, wenn ich ruby-Annotationen verwende? Das wurde schon oft angefragt und Houdini sollte diese Wünsche erfüllen.

Das war noch nicht alles!

Die Liste der Entwürfe von Houdini enthält noch mehr Spezifikationen, aber ihre Zukunft ist eher ungewiss und sie sind nicht viel mehr als Platzhalter für Ideen. Beispiele hierfür sind benutzerdefiniertes Überlaufverhalten, die CSS-Syntax-Erweiterungs-API, die Erweiterung des nativen Scrollverhaltens und ähnlich ehrgeizige Dinge, die alles auf der Webplattform ermöglichen, was zuvor nicht möglich war.

Demos

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