Houdini – sposób obalania mitów na wczesnym etapie

Czy zastanawiałeś/się kiedyś, ile pracy wykonuje usługa CSS? Zmieniasz jeden atrybut, a nagle cała witryna ma inny układ. To rodzaj magii. Do tej pory my, czyli społeczność programistów internetowych, mogliśmy tylko przyglądać się tej magii. A co, jeśli chcemy stworzyć własną magię? Co zrobić, jeśli chcemy stać się magami?

Witaj Houdini!

Zespół zadaniowy Houdini składa się z inżynierów z Mozilla, Apple, Opera, Microsoft, HP, Intel i Google, którzy współpracują ze sobą, aby udostępnić deweloperom webowym niektóre części silnika CSS. Grupa robocza pracuje nad kolekcją projektów, których celem jest uzyskanie akceptacji W3C i ustanowienie ich jako standardów internetowych. Określili sobie kilka ogólnych celów i przekształcili je w wersje robocze konkretnie. To z kolei doprowadziło do utworzenia zestawu dodatkowych, niższego poziomu wersji roboczych specyfikacji.

Kolekcja tych wersji roboczych to to, co zwykle ma na myśli użytkownik, gdy mówi o „Houdini”. W momencie pisania tego artykułu lista projektów jest niekompletna, a niektóre z nich to tylko placeholdery.

Specyfikacje

Worklety (spec)

Same worklety nie są zbyt przydatne. Są to koncepcje wprowadzone, aby umożliwić tworzenie wielu późniejszych wersji. Jeśli po przeczytaniu słowa „worklet” pomyślałeś/pomyślałaś o Web Workers, masz rację. Pod względem koncepcyjnym te pojęcia bardzo się pokrywają. Dlaczego mamy wprowadzać nowe rozwiązania, skoro mamy już pracowników?

Celem Houdini jest udostępnienie nowych interfejsów API, aby umożliwić deweloperom stron internetowych podłączenie ich własnego kodu do silnika CSS i otaczających go systemów. Można przypuszczać, że niektóre z tych fragmentów kodu będą musiały być wykonywane w każdej klatce. Niektóre muszą z definicji. Cytując specyfikację usługi Web Worker:

Oznacza to, że wątki internetowe nie nadają się do tego, co planuje Houdini. Dlatego wymyśliliśmy worklety. Elementy worklet korzystają z klas ES2015 do definiowania zbioru metod, których sygnatury są wstępnie zdefiniowane przez typ elementu worklet. Są lekkie i krótkotrwałe.

Interfejs API usługi porównywania cen (specyfikacja)

Interfejs Paint API jest domyślnie włączony w Chrome 65. Przeczytaj szczegółowe wprowadzenie.

Worklet kompozytora

Opisany tutaj interfejs API jest nieaktualny. Element kompozytorski został przeprojektowany i jest teraz proponowany jako „Element kompozytorski animacji”. Dowiedz się więcej o obecnej wersji interfejsu API.

Mimo że specyfikacja workleta kompozytora została przeniesiona do WICG i będzie ulepszana, to właśnie ona najbardziej mnie ekscytuje. Niektóre operacje są zlecane przez silnik CSS do karty graficznej komputera, ale zależy to od karty graficznej i ogólnego stanu urządzenia.

Przeglądarka zwykle pobiera drzewo DOM i na podstawie określonych kryteriów decyduje, które gałęzie i poddrzewa mają mieć własną warstwę. Te podrzędne drzewa malują się na nim (w przyszłości być może za pomocą farby). W ostatnim kroku wszystkie te pojedyncze, już namalowane warstwy są ułożone jedna na drugiej z uwzględnieniem indeksów z poziomu Z, przekształceń 3D itp., aby uzyskać obraz widoczny na ekranie. Ten proces nazywa się kompozycją i jest wykonywany przez kompozytor.

Zaletą procesu komponowania jest to, że nie musisz ponownie malować wszystkich elementów przy maleńkim przewijaniu strony. Zamiast tego możesz ponownie użyć warstw z poprzedniego kadru i ponownie uruchomić kompozytor z aktualną pozycją przewijania. To przyspiesza działanie. Pomoże nam to osiągnąć 60 FPS.

Worklet kompozytora.

Jak sama nazwa wskazuje, Worklet kompozytora umożliwia zaczepienie się kompozytora i wpływanie na sposób, w jaki warstwa elementu, która została już pomalowana, jest umieszczana i nakładana na inne warstwy.

Żeby bardziej sprecyzować, możesz wskazać przeglądarce, że chcesz podłączyć się do procesu komponowania określonego węzła DOM i poprosić o dostęp do określonych atrybutów, takich jak pozycja przewijania, transform czy opacity. Wymusza to użycie tego elementu na jego własnej warstwie i w każdej klatce wywołuje kod. Możesz przesuwać warstwę, manipulować jej transformacją i zmieniać jej atrybuty (np. opacity), co pozwala na wykonywanie skomplikowanych działań z prędkością 60 FPS.

Oto pełna implementacja przewijania paralaksy z użyciem listy kompozytora.

// 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 napisał polyfill dla workletu kompozytora, więc możesz go wypróbować – oczywiście przy znacznie większym wpływie na wydajność.

Worklet układu (spec)

Zaproponowano pierwszą wersję roboczą rzeczywistej specyfikacji. Implementacja nie wymaga czasu.

Ponownie specyfikacja jest praktycznie pusta, ale pomysł jest intrygujący: napisz własny układ! Worklet układu ma umożliwić wykonanie display: layout('myLayout') i uruchomienie kodu JavaScript, aby uporządkować elementy podrzędne węzła w polu węzła.

Oczywiście uruchamianie pełnej implementacji JavaScript układu flex-box w CSS jest wolniejsze niż uruchamianie równoważnej implementacji natywnej, ale łatwo wyobrazić sobie scenariusz, w którym pominięcie niektórych elementów może przynieść wzrost wydajności. Wyobraź sobie stronę internetową składającą się wyłącznie z płytek, tak jak w Windows 10 lub w układzie typu masonry. Nie używa się pozycjonowania bezwzględnego ani stałego, ani z-index. Elementy nie mogą się na siebie nakładać ani mieć żadnych ramek ani przepełnienia. Możliwość pominięcia wszystkich tych kontroli podczas ponownego układania może przynieść wzrost wydajności.

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
        };
    }
});

Typowany obiekt CSSOM (spec)

Typowany model obiektów CSSOM (model obiektów arkuszy stylów kaskadowych) rozwiązuje problem, z którym prawdopodobnie wszyscy się spotkaliśmy i zwykliśmy go tolerować. Oto przykładowy kod JavaScript:

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

Wykonujemy obliczenia, konwertując liczbę na ciąg znaków, aby dodać jednostkę, a następnie przeglądarka przeanalizuje ten ciąg znaków i przekształci go z powrotem w liczbę dla silnika CSS. Sytuacja staje się jeszcze gorsza, gdy zmieniasz transformacje za pomocą JavaScripta. To już wszystko. Kod CSS będzie teraz wpisywany.

Ta wersja robocza jest jedną z bardziej dopracowanych wersji, nad którą właśnie pracujemy nad kodem polyfill. (Oświadczenie: używanie polyfilla będzie oczywiście jeszcze bardziej obciążać procesor. Chodzi o to, aby pokazać, jak wygodnie można korzystać z interfejsu API.)

Zamiast ciągów tekstowych będziesz pracować nad elementem StylePropertyMap, gdzie każdy atrybut CSS ma swój klucz i odpowiadający mu typ wartości. Atrybuty takie jak width mają typ wartości LengthValue. LengthValue to słownik wszystkich jednostek CSS, takich jak em, rem, px, percent itd. Ustawienie height: calc(5px + 5%) da wynik LengthValue{px: 5, percent: 5}. Niektóre właściwości, takie jak box-sizing, akceptują tylko określone słowa kluczowe i dlatego mają typ wartości KeywordValue. W tym celu można sprawdzić ich ważność w czasie działania.

<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}

Właściwości i wartości

(specyfikacja)

Czy wiesz, czym są właściwości niestandardowe w CSS (lub ich nieoficjalny alias „zmienniki w CSS”)? Oto one, ale z typami. Do tej pory zmienne mogły mieć tylko wartości ciągu znaków i wykorzystywały proste wyszukiwanie i zastępowanie. Ten projekt umożliwiłby nie tylko określenie typu zmiennych, ale też zdefiniowanie wartości domyślnej i wpływanie na zachowanie dziedziczenia za pomocą interfejsu JavaScript API. Teoretycznie umożliwiłoby to również animowanie właściwości niestandardowych za pomocą standardowych przejść i animacji CSS, co również jest rozważane.

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

Dane dotyczące czcionek

Dokładnie widać dane czcionek. Co to jest ramka ograniczająca (lub ramki ograniczające) podczas renderowania ciągu X za pomocą czcionki Y w rozmiarze Z? Co się stanie, jeśli użyję adnotacji Ruby? Wiele osób prosiło o to, a Houdini w końcu spełniło ich życzenie.

Poczekaj, to jeszcze nie wszystko.

Na liście projektów w Houdini jest jeszcze więcej specyfikacji, ale ich przyszłość jest raczej niepewna i są one raczej tylko miejscem na pomysły. Przykłady obejmują niestandardowe zachowania przepełnienia, interfejs API rozszerzenia składni CSS, rozszerzenie zachowania natywnego przewijania i podobnie ambitne funkcje, które umożliwiają wykonywanie na platformie internetowej czynności, które wcześniej były niemożliwe.

Prezentacje

Udostępniłem w źródle kod wersji demonstracyjnej (demo na żywo korzystające z polyfill).