Houdini - Demistificazione dei CSS

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

Entra 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 farle accettare dal W3C in modo che diventino standard web effettivi. 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 leggi "worklet", non ti sbagli. Questi concetti presentano molti 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. Citazione delle specifiche dei worker web:

Ciò significa che i web worker non sono attuabili per le cose che Houdini intende fare. Di conseguenza, 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 (spec)

L'API Paint è abilitata 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 di animazione". Scopri di più sull'iterazione attuale dell'API.

Anche se le specifiche del worklet del compositore sono state spostate in WICG, sono quelle che più mi entusiasmano. Alcune operazioni vengono affidate in outsourcing 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 l'albero DOM e, in base a criteri specifici, decide di assegnare ad alcuni rami e sottoalberi il proprio livello. Questi sottoalberi si dipingono su di essi (forse usando un foglio di lavoro di vernice in futuro). Come ultimo passaggio, tutti questi singoli livelli, ora dipinti, sono impilati e posizionati uno sopra l'altro, rispettando gli indici z, le trasformazioni 3D e così via, per ottenere l'immagine finale visibile sullo schermo. Questo processo è chiamato compositing e viene eseguito dal compositore.

Il vantaggio del processo di composizione è che non è necessario ricolorare tutti gli elementi quando la pagina scorre un po'. Puoi invece riutilizzare i livelli del frame precedente ed eseguire nuovamente il compositore con la posizione di scorrimento aggiornata. In questo modo tutto è veloce. In questo modo possiamo raggiungere i 60 f/s.

Worklet del compositore.

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

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 proprio livello e viene chiamato su ogni frame. Puoi spostare il tuo livello modificando la trasformazione degli strati e modificandone gli attributi (come opacity), in modo da fare cose stravaganti a 60 fps.

Ecco un'implementazione completa per lo scorrimento con parallasse, utilizzando 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 in modo che tu possa fare una prova, ovviamente con un impatto sulle prestazioni molto maggiore.

Worklet di layout (spec)

È stata proposta la prima bozza della specifica reale. L'implementazione è ancora da tempo.

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

Ovviamente, l'esecuzione di un'implementazione JavaScript completa del layout flex-box di CSS è più lenta di un'implementazione nativa equivalente, ma è facile immaginare uno scenario in cui tagliare i angoli può produrre un miglioramento delle prestazioni. Immagina un sito web composto solo da riquadri, come Windows 10 o un layout in stile muratura. Non viene utilizzato 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 durante il relayout potrebbe portare a 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 (spec)

Il CSSOM digitato (CSS Object Model o Cascading Style Sheets Object Model) risolve un problema che probabilmente tutti abbiamo incontrato e che abbiamo appena imparato a gestire. Facciamo un'illustrazione con una riga di JavaScript:

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

Stiamo facendo dei calcoli, convertendo un numero in una stringa per aggiungere un'unità solo affinché il browser analizzi quella stringa e la riconvertisca in un numero per il motore CSS. Questo comportamento diventa ancora più brutto quando manipoli le trasformazioni con JavaScript. Addio, Il CSS sta per iniziare a digitare.

Questa bozza è una delle più mature e il polyfill è già in fase di sviluppo. (Disclaimer: l'utilizzo del polyfill aggiunge ovviamente ancora più overhead di calcolo. Lo scopo è dimostrare la praticità dell'API.

Invece delle stringhe, lavorerai sull'elemento StylePropertyMap di un elemento, in cui ogni attributo CSS ha la propria chiave e il 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à personalizzate CSS (o il relativo alias non ufficiale "Variabili CSS")? Questi sono loro, ma con i tipi! Finora le variabili potevano avere solo valori stringa e usare 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 dell'ereditarietà utilizzando un'API JavaScript. Tecnicamente, ciò consentirebbe anche alle proprietà personalizzate di essere animate con transizioni e animazioni CSS standard, un elemento anch'esso preso in considerazione.

["--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 sono esattamente come appaiono. Che cos'è il riquadro di delimitazione (o i riquadri di delimitazione) quando eseguo il rendering della stringa X con il carattere Y di dimensione Z? Cosa succede se uso le annotazioni Ruby? Houdini è molto richiesta e Houdini dovrebbe finalmente realizzare questi desideri.

Ma non è tutto.

L'elenco delle bozze di Houdini contiene ancora più specifiche, ma il futuro di queste bozze è piuttosto incerto e non sono molto altro che segnaposto per idee. Alcuni esempi includono comportamenti di overflow personalizzati, API di estensione della sintassi CSS, estensione del comportamento di scorrimento nativo e elementi simili ambiziosi, che consentono cose sulla piattaforma web che prima non erano possibili.

Demo

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