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 trovare 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. 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, che li hanno trasformati in bozze di specifiche che a loro volta hanno dato vita a una serie di bozze di specifiche di supporto di livello inferiore.

La raccolta di queste bozze è ciò che di solito si intende quando qualcuno 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 davvero utili. Sono un concetto introdotto per rendere possibili molte delle bozze successive. Se hai pensato ai ("web worker") quando hai letto "worklet", non hai sbagliato. Hanno molte sovrapposizioni concettuali. Ma perché c'è 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 debbano essere eseguiti ogni singolo frame. Alcune devono essere definite per definizione. Citando le specifiche di Web Worker:

Ciò significa che i web worker non sono utilizzabili per ciò che Houdini intende fare. Pertanto, sono stati inventati i worklet. 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 (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 su di esso (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 e viene 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 tutto è veloce. 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 ulteriori dettagli, puoi indicare al browser di eseguire l'hook al processo di composizione per un determinato nodo DOM e richiedere l'accesso a determinati attributi come la posizione di scorrimento, transform o opacity. In questo modo, l'elemento viene inserito nel suo proprio livello e il codice viene richiamato 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 in fase di sviluppo.

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 JavaScript per organizzare i figli di un nodo all'interno del riquadro del nodo.

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 digitato (specifica)

Il CSSOM (CSS Object Model o Cascading Style Sheets Object Model) 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. Questo comportamento diventa ancora più brutto 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. Il tipo di valore degli attributi come width è LengthValue. 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 essere verificata in fase di 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à CSS personalizzate (o il loro alias non ufficiale "Voci CSS")? Eccoli, ma con i tipi. Finora le variabili potevano avere solo valori stringa e usare 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 relative ai 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 uso le 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).