Houdini - Demistificazione dei CSS

Hai mai pensato alla quantità di lavoro svolta dal CSS? Modifichi un singolo attributo e improvvisamente l'intero sito web viene visualizzato in un layout diverso. È un po' di magia. Finora, noi, la community di sviluppatori web, abbiamo potuto solo assistere e osservare la magia. E se volessimo creare la nostra magia? E se volessimo diventare il mago?

Entra in Houdini.

Il gruppo di lavoro Houdini è composto da ingegneri di Mozilla, Apple, Opera, Microsoft, HP, Intel e Google che collaborano per esporre determinate parti del motore CSS agli sviluppatori web. Il gruppo di lavoro sta lavorando a una raccolta di bozze con l'obiettivo di farle accettare dal W3C per diventare veri e propri standard web. Si sono posti alcuni obiettivi di alto livello, li hanno trasformati in bozze di specifiche che a loro volta hanno dato vita a un insieme di bozze di specifiche di supporto di livello inferiore.

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

Le specifiche

Worklet (spec)

I worklet da soli non sono molto utili. Si tratta di un concetto introdotto per consentire molte delle bozze successive. Se hai pensato ai worker web quando hai letto "worklet", non hai sbagliato. Hanno molte sovrapposizioni concettuali. Quindi, perché una nuova funzionalità quando abbiamo già 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 è irrealistico presumere che alcuni di questi frammenti di codice dovranno essere eseguiti in ogni singolo frame. Alcuni devono essere obbligatoriamente per definizione. Citando le specifiche di Web Worker:

Ciò significa che i worker web non sono adatti alle attività che Houdini intende svolgere. Per questo motivo sono stati inventati i worklet. I worklet utilizzano le classi ES2015 per definire una raccolta di metodi le cui firme sono predefinite dal tipo di worklet. Sono leggeri e di breve durata.

API CSS Paint (specifica)

L'API Paint è abilitata per impostazione predefinita in Chrome 65. Leggi la introduzione dettagliata.

Worklet del compositore

L'API descritta qui è obsoleta. Il worklet Compositor è stato nuovamente progettato e ora viene proposto come "Animation Worklet". Scopri di più sull'attuale iterazione dell'API.

Anche se la specifica del worklet del compositore è stata spostata nel WICG e verrà migliorata, è quella che mi entusiasma di più. Alcune operazioni vengono esternalizzate alla scheda grafica del computer dall'engine CSS, anche se questo dipende sia dalla scheda grafica sia dal dispositivo in generale.

In genere, un browser prende l'albero DOM e, in base a criteri specifici, decide di assegnare a alcuni rami e sottoalberi un proprio livello. Questi sottoalberi vengono dipinti sopra (forse utilizzando un worklet di pittura in futuro). Come passaggio finale, tutti questi singoli livelli, ora dipinti, 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 ed è eseguito dal compositore.

Il vantaggio della procedura di composizione è che non devi ridisegnare tutti gli elementi quando la pagina scorre leggermente. Puoi invece riutilizzare i livelli del frame precedente ed eseguire di nuovo il compositore con la posizione di scorrimento aggiornata. In questo modo, le cose vanno veloci. In questo modo possiamo raggiungere i 60 fps.

Worklet del compositore.

Come suggerisce il nome, il worklet del compositore ti consente di collegarti 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 essere un po' più specifico, puoi dire al browser che vuoi collegarti al processo di composizione per un determinato nodo DOM e puoi richiedere l'accesso ad alcuni attributi come la posizione di scorrimento, transform o opacity. In questo modo, l'elemento viene inserito nel suo livello e il codice viene chiamato in ogni fotogramma. Puoi spostare il livello manipolando la trasformazione dei livelli e modificandone gli attributi (ad esempio opacity) consentendoti di fare cose fantastiche a 60 fps.

Di seguito è riportata un'implementazione completa per lo scorrimento parallasse, che utilizza il worklet del 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, quindi puoi provarlo, ovviamente con un impatto sulle prestazioni molto più elevato.

Worklet di layout (spec)

È stata proposta la prima bozza delle specifiche reali. L'implementazione è ancora lontana.

Anche in questo caso, le specifiche sono praticamente vuote, ma il concetto è intrigante: scrivi il tuo layout. Il worklet di layout dovrebbe consentirti di eseguire display: layout('myLayout') ed eseguire il codice JavaScript per organizzare i figli di un nodo nella relativa casella.

Ovviamente, l'esecuzione di un'implementazione completa in JavaScript del layout flex-box del CSS è più lenta rispetto all'esecuzione di un'implementazione nativa equivalente, ma è facile immaginare uno scenario in cui tagliare i costi può comportare un aumento delle prestazioni. Immagina un sito web costituito solo da riquadri, come Windows 10 o un layout in stile mattonella. Non vengono utilizzati il posizionamento assoluto e fisso, né z-index, né gli elementi si sovrappongono o hanno alcun tipo di bordo o overflow. La possibilità di saltare tutti questi controlli sul nuovo layout potrebbe comportare un miglioramento delle 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 tipizzato (specifiche)

Il CSSOM (CSS Object Model o Cascading Style Sheets Object Model) con tipi risolve un problema che probabilmente abbiamo riscontrato tutti e che abbiamo imparato a tollerare. Vediamo un esempio con una riga di JavaScript:

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

Stiamo facendo matematica, convertendo un numero in una stringa per aggiungere un'unità solo per fare in modo che il browser analizzi la stringa e la converta di nuovo in un numero per il motore CSS. Il problema peggiora quando manipoli le trasformazioni con JavaScript. Non più. CSS sta per ricevere un po' di digitazione.

Questa bozza è una delle più mature e su un polyfill è già in corso di lavoro. Disclaimer: l'utilizzo del polyfill ovviamente aggiunge ancora di più il sovraccarico computazionale. Il punto è mostrare quanto sia comoda l'API.

Anziché con le stringhe, lavorerai con StylePropertyMap di un elemento, dove ogni attributo CSS ha la propria chiave e il proprio 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%) restituirà un LengthValue{px: 5, percent: 5}. Alcune proprietà come box-sizing accettano solo determinate parole chiave e, pertanto, hanno un tipo di valore KeywordValue. La validità di questi attributi potrebbe quindi essere verificata in fase di esecuzione.

<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à CSS personalizzate (o il loro alias non ufficiale "Voci CSS")? Eccoli, ma con i tipi. Finora, le variabili potevano avere solo valori di stringa e utilizzavano un semplice approccio di ricerca e sostituzione. Questa bozza ti consentirebbe non solo di specificare un tipo per le variabili, ma anche di definire un valore predefinito e di influenzare il comportamento dell'ereditarietà utilizzando un'API JavaScript. Tecnicamente, questo consentirebbe anche di animare le proprietà personalizzate con transizioni e animazioni CSS standard, che sono in fase di valutazione.

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

Metriche dei caratteri

Le metriche dei caratteri sono esattamente ciò che sembrano. Qual è il riquadro di delimitazione (o i riquadri di delimitazione) quando viene visualizzata la stringa X con il carattere Y in dimensione Z? Cosa succede se utilizzo annotazioni Ruby? Questa funzionalità è stata molto richiesta e Houdini dovrebbe finalmente farla diventare realtà.

Ma non è tutto!

Esistono altre specifiche nell'elenco delle bozze di Houdini, ma il loro futuro è piuttosto incerto e non sono altro che segnaposto per le idee. Alcuni esempi sono i comportamenti di overflow personalizzati, l'API di estensione della sintassi CSS, l'estensione del comportamento di scorrimento nativo e altre funzionalità altrettanto ambiziose, che consentono di fare sulla piattaforma web cose che prima non erano possibili.

Demo

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