Houdini - Demistificazione dei CSS

Hai mai pensato alla quantità di lavoro che svolge il CSS? Modificando un singolo attributo, l'intero sito web viene visualizzato improvvisamente in un layout diverso. È una sorta di magia. Finora, noi, la community di sviluppatori web, siamo riusciti solo a vedere e osservare la magia. E se volessimo creare la nostra magia? E se volessimo diventare il mago?

Entra in Houdini!

La task force di Houdini è composta da ingegneri di Mozilla, Apple, Opera, Microsoft, HP, Intel e Google che lavorano insieme per esporre determinate parti del motore CSS agli sviluppatori web. La task force sta lavorando a una raccolta di bozze con l'obiettivo di fare in modo che vengano accettate dal W3C affinché diventino veri standard web. Si sono posti alcuni obiettivi generali, trasformandoli in bozze di specifiche che a loro volta hanno dato vita a una serie di bozze di supporto di specifiche di livello inferiore.

La raccolta di queste bozze è in genere ciò che si intende quando si parla di "Houdini". Al momento della stesura di questo documento, l'elenco delle bozze è incompleto e alcune sono dei semplici segnaposto.

Le specifiche

Worklet (spec)

I worklet di per sé non sono molto utili. un concetto introdotto per rendere possibili molte delle bozze successive. Se hai pensato ai web worker quando leggi "worklet", non ti sbagli. e presentano una notevole sovrapposizione concettuale. Perché una cosa nuova quando abbiamo già dei lavoratori?

L'obiettivo di Houdini è esporre nuove API per consentire agli sviluppatori web di collegare il proprio codice al motore CSS e ai sistemi circostanti. Probabilmente non è realistico supporre che alcuni di questi frammenti di codice dovranno essere eseguiti ogni singolo frame. Alcuni devono essere per definizione. Citando la specifica di Web Worker:

Ciò significa che i web worker non sono fattibili per le attività che Houdini ha in mente di fare. Pertanto, i worklet sono stati inventati. I worklet utilizzano le classi ES2015 per definire una raccolta di metodi, le cui firme sono predefinite in base al tipo di worklet. Sono leggeri e di breve durata.

API CSS Paint (spec)

L'API Paint è attivata per impostazione predefinita in Chrome 65. Leggi l'introduzione dettagliata.

Worklet del compositore

L'API qui descritta è obsoleta. Il worklet del compositore è stato riprogettato e ora viene proposto come "Worklet dell'animazione". Ulteriori informazioni sull'iterazione attuale dell'API.

Anche se le specifiche del worklet del compositore sono state spostate nel WICG e verranno aggiornate, sono quelle che mi entusiasmano di più. Alcune operazioni vengono esternalizzate alla scheda grafica del computer dal motore CSS, anche se questo dipende dalla scheda grafica e dal dispositivo in generale.

In genere un browser prende la struttura DOM e, in base a criteri specifici, decide di assegnare un proprio livello ad alcuni rami e sottoalberi. Questi sottoalberi si dipingono da soli (magari usando in futuro un worklet di colori). Infine, tutti questi singoli livelli vengono impilati e posizionati uno sopra l'altro, rispettando gli indici z, le trasformazioni 3D e così via, per produrre l'immagine finale visibile sullo schermo. Questo processo, chiamato compositing, viene eseguito dal compositore.

Il vantaggio del processo di composizione è che non è necessario fare in modo che tutti gli elementi si ridipingano da soli quando la pagina scorre leggermente. Puoi invece riutilizzare i livelli del frame precedente ed eseguire nuovamente il compositor con la posizione di scorrimento aggiornata. Questo rende tutto più veloce. Questo ci aiuta a raggiungere i 60 fps.

Worklet del compositore.

Come suggerisce il nome, il worklet del compositore ti consente di agganciarti al compositore e di influenzare il modo in cui il livello di un elemento, che è già stato dipinto, viene posizionato e sovrapposto agli altri livelli.

Per ottenere informazioni un po' più specifiche, puoi dire al browser che vuoi agganciarti al processo di composizione per un determinato nodo DOM e puoi richiedere l'accesso a determinati attributi come la posizione di scorrimento, transform o opacity. In questo modo l'elemento viene forzato sul suo livello e il codice viene chiamato in ogni frame. Puoi spostare il livello modificando la trasformazione dei livelli e modificandone gli attributi (ad esempio opacity) in modo da poter fare attività creative a ben 60 fps.

Ecco un'implementazione completa per lo scorrimento in parallasse, utilizzando il worklet compositore.

// 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 ha scritto un polyfill per il worklet del compositore in modo che tu possa provarlo, ovviamente con un impatto sulle prestazioni molto più elevato.

Worklet del layout (spec)

È stata proposta la prima bozza delle specifiche effettiva. L'implementazione è un po' distante.

Anche in questo caso, le specifiche sono praticamente vuote, ma il concetto è interessante: scrivi il tuo layout. Il worklet di layout dovrebbe consentirti di eseguire display: layout('myLayout') ed eseguire JavaScript per disporre gli elementi secondari di un nodo nella casella del nodo.

Ovviamente, l'esecuzione di un'implementazione JavaScript completa del layout flex-box di CSS è più lenta rispetto all'esecuzione di un'implementazione nativa equivalente, ma è facile immaginare uno scenario in cui la riduzione degli angoli può migliorare le prestazioni. Immagina un sito web costituito solo da riquadri, come Windows 10 o un layout in stile muratura. Il posizionamento assoluto e fisso non viene utilizzato, né lo è z-index, né gli elementi si sovrappongono o presentano alcun tipo di bordo o overflow. Essere in grado di saltare tutti questi controlli al momento del re-layout potrebbe migliorare le prestazioni.

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

CSSOM digitato (spec)

I tipi di CSSOM (CSS Object Model o Cascading Style Fogli Object Model) risolvono un problema che probabilmente tutti abbiamo incontrato e che abbiamo appena imparato. Vediamo con una riga di JavaScript:

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

Stiamo facendo calcoli: convertiamo un numero in una stringa per aggiungere un'unità solo per consentire al browser di analizzare la stringa e convertirla in un numero per il motore CSS. Ciò diventa ancora più brutto quando manipolari le trasformazioni con JavaScript. Mai più! CSS sta per iniziare a digitare.

Questa bozza è una di quelle più mature ed è già in fase di sviluppo un polyfill. (Disclaimer: l'utilizzo del polyfill aggiungerà ovviamente ancora più all'overhead di calcolo. Il punto è mostrare quanto è pratica l'API).

Invece delle stringhe, lavorerai sul StylePropertyMap di un elemento, dove ogni attributo CSS ha una propria chiave e un tipo di valore corrispondente. Gli attributi come width hanno LengthValue come tipo di valore. Un LengthValue è un dizionario di tutte le unità CSS come em, rem, px, percent e così via. L'impostazione height: calc(5px + 5%) produrrà un LengthValue{px: 5, percent: 5}. Alcune proprietà come box-sizing accettano solo determinate parole chiave e, di conseguenza, hanno un tipo di valore KeywordValue. La validità di questi attributi può quindi essere verificata durante il runtime.

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

Proprietà e valori

(spec)

Conosci le proprietà personalizzate CSS (o il loro alias non ufficiale "Variabili CSS")? Eccoli, ma con tipi! Finora le variabili potevano avere solo valori stringa e usato un semplice approccio di ricerca e sostituzione. Questa bozza ti consente non solo di specificare un tipo per le variabili, ma anche di definire un valore predefinito e influenzare il comportamento di ereditarietà utilizzando un'API JavaScript. Tecnicamente, ciò consentirebbe anche di animare le proprietà personalizzate con transizioni e animazioni CSS standard, come sempre.

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

Metriche relative ai caratteri

Le metriche relative ai caratteri indicano esattamente il loro aspetto. Che cos'è il riquadro di delimitazione (o i riquadri di delimitazione) quando eseguo il rendering della stringa X con il carattere Y alle dimensioni Z? Che cosa succede se uso annotazioni rugby? È stata richiesta molte volte e Houdini dovrebbe finalmente esaudire i suoi desideri.

Ma non è tutto.

Nell'elenco di bozze di Houdini ci sono ancora più specifiche, ma il futuro è piuttosto incerto e non sono molto più di semplici segnaposto di idee. Alcuni esempi includono comportamenti di overflow personalizzati, API di estensione della sintassi CSS, l'estensione del comportamento di scorrimento nativo e funzioni analoghe ambiziose che consentono di realizzare cose sulla piattaforma web che prima non erano possibili.

Demo

Ho reso open source il codice per la demo (demo dal vivo utilizzando polyfill).