Houdini – Demystifying CSS

Avez-vous déjà pensé à la quantité de travail que les CSS effectuent ? Vous modifiez un seul attribut, et soudainement, l'intégralité de votre site Web apparaît dans une mise en page différente. C'est une sorte de magie. Jusqu'à présent, nous, la communauté des développeurs Web, n'avons pu assister et observer que la magie. Et si nous voulons trouver notre propre magie ? Et si nous voulions devenir le magicien ?

C'est Houdini !

Le groupe de travail Houdini est composé d'ingénieurs de Mozilla, Apple, Opera, Microsoft, HP, Intel et Google qui travaillent ensemble pour présenter certaines parties du moteur CSS aux développeurs Web. Cette équipe travaille sur une collection de brouillons dont l'objectif est de les faire accepter par le W3C en tant que normes Web réelles. Ils se sont fixé quelques objectifs de haut niveau, puis les ont transformés en brouillons de spécifications, qui ont donné naissance à un ensemble de brouillons de spécifications complémentaires de niveau inférieur.

C'est en général à cette occasion que l'on parle de "Houdini". Au moment de la rédaction de ce document, la liste des brouillons est incomplète, et certains sont de simples espaces réservés.

Les spécifications

Worklets (spec)

Les Worklets en eux-mêmes ne sont pas vraiment utiles. Ils sont un concept introduit pour rendre la plupart des versions ultérieures possibles. Si vous avez pensé à Web Workers lorsque vous lisez "worklet", vous n'avez pas tort. Les concepts se recoupent souvent. Alors pourquoi une nouvelle chose alors que nous avons déjà des travailleurs ?

L'objectif d'Houdini est d'exposer de nouvelles API pour permettre aux développeurs Web de connecter leur propre code au moteur CSS et aux systèmes environnants. Il n'est probablement pas irréaliste de supposer que certains de ces fragments de code devront être exécutés chaque frame. Certains d’entre eux doivent par définition. Mentionnez la spécification Web Worker:

Les travailleurs Web ne sont pas viables pour ce que Houdini prévoit de faire. C'est pourquoi Houdini a inventé les worklets. Les Worklets utilisent des classes ES2015 pour définir un ensemble de méthodes, dont les signatures sont prédéfinies en fonction du type de worklet. Ils sont légers et de courte durée.

API CSS Paint (spec)

L'API Paint est activée par défaut dans Chrome 65. Lisez l'introduction détaillée.

Worklet compositeur

L'API décrite ici est obsolète. Le worklet de compositeur a été repensé et est désormais proposé sous le nom de "Worklet d'animation". Apprenez-en plus sur l'itération actuelle de l'API.

Même si la spécification du worklet du compositeur a été déplacée vers le WICG et fait l'objet d'une itération, c'est celle qui me plaît le plus. Certaines opérations sont externalisées pour la carte graphique de votre ordinateur par le moteur CSS, bien que cela dépend à la fois de votre carte graphique et de votre appareil en général.

Un navigateur utilise généralement l'arborescence DOM et, en fonction de critères spécifiques, décide de donner à certaines branches et sous-arborescences leur propre couche. Ces sous-arbres se peignent dessus (peut-être à l'aide d'un atelier de peinture à l'avenir). Pour finir, tous ces calques individuels, maintenant peints, sont empilés et positionnés les uns sur les autres, en respectant les z-indices, les transformations 3D, etc., pour produire l'image finale visible à l'écran. Ce processus est appelé composition et est exécuté par le compositeur.

L'avantage du processus de composition est que vous n'avez pas besoin de repeindre tous les éléments eux-mêmes lorsque la page défile un tout petit peu. À la place, vous pouvez réutiliser les calques de l'image précédente et simplement réexécuter le compositeur avec la position de défilement mise à jour. Cela accélère les choses. Cela nous aide à atteindre 60 FPS.

Worklet compositeur.

Comme son nom l'indique, le worklet compositeur vous permet de vous connecter au compositeur et d'influencer la façon dont le calque d'un élément, qui a déjà été peint, est positionné et superposé aux autres couches.

Pour être un peu plus précis, vous pouvez indiquer au navigateur que vous souhaitez vous rattacher au processus de composition pour un certain nœud DOM et demander l'accès à certains attributs tels que la position de défilement, transform ou opacity. Cela force cet élément sur sa propre couche et sur chaque frame, votre code est appelé. Vous pouvez déplacer votre calque en manipulant la transformation et en modifiant ses attributs (comme opacity), ce qui vous permet de faire des trucs fantaisistes à une fréquence incroyable de 60 FPS.

Voici une implémentation complète du défilement parallaxe, à l'aide du worklet du compositeur.

// 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 Flak a écrit un polyfill pour le worklet de compositeur, pour que vous puissiez l'essayer, avec un impact bien plus important sur les performances.

Worklet de mise en page (spec)

La première ébauche réelle des spécifications a été proposée. L'implémentation est encore disponible.

Là encore, la spécification correspondante est pratiquement vide, mais le concept est fascinant: vous pouvez écrire votre propre mise en page. Le worklet de mise en page est censé vous permettre d'effectuer des opérations display: layout('myLayout') et d'exécuter votre code JavaScript pour organiser les enfants d'un nœud dans la zone du nœud.

Bien sûr, l'exécution d'une implémentation JavaScript complète de la mise en page flex-box de CSS est plus lente que l'exécution d'une implémentation native équivalente, mais il est facile d'imaginer un scénario dans lequel brûler les étapes peut vous permettre d'améliorer les performances. Imaginez un site Web composé uniquement de tuiles, comme Windows 10 ou une mise en page de style maçonnerie. Les positionnements absolus et fixes ne sont pas utilisés, pas plus que z-index. Les éléments ne se chevauchent pas ou ne comportent jamais de bordure ni de dépassement. Le fait de pouvoir ignorer toutes ces vérifications lors de la remise en page peut améliorer les performances.

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

Code CSSOM typé (spec)

La méthode CSSOM (CSS Object Model) ou Cascading Style Sheets Object Model (modèle d'objet de feuilles de style en cascade) est une solution que nous avons probablement tous rencontrée et que nous venons d'apprendre à régler. Prenons une ligne de code JavaScript:

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

Nous effectuons des calculs : nous convertissons un nombre en chaîne pour ajouter une unité afin que le navigateur analyse cette chaîne et la reconvertisse en nombre pour le moteur CSS. Cela devient encore plus gênant lorsque vous manipulez des transformations avec JavaScript. Ce n'est plus le cas. Vous allez bientôt devoir saisir du texte dans CSS.

Ce brouillon est l'un des plus matures et un polyfill est déjà en cours de développement. (Clause de non-responsabilité: l'utilisation du polyfill entraînera évidemment encore plus de frais de calcul. L'objectif est de montrer à quel point l'API est pratique.)

Au lieu de chaînes, vous allez travailler sur l'StylePropertyMap d'un élément, où chaque attribut CSS a sa propre clé et le type de valeur correspondant. Les attributs tels que width ont LengthValue comme type de valeur. Un LengthValue est un dictionnaire de toutes les unités CSS telles que em, rem, px, percent, etc. Si vous définissez height: calc(5px + 5%), vous obtiendrez une LengthValue{px: 5, percent: 5}. Certaines propriétés telles que box-sizing acceptent uniquement certains mots clés et possèdent donc un type de valeur KeywordValue. La validité de ces attributs peut ensuite être vérifiée au moment de l'exécution.

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

Propriétés et valeurs

(spec)

Connaissez-vous les propriétés CSS personnalisées (ou leur alias non officiel "Variables CSS") ? C'est eux, mais avec des types ! Jusqu'à présent, les variables ne pouvaient avoir que des valeurs de chaîne et utilisaient une approche de recherche et de remplacement simple. Ce brouillon vous permettrait non seulement de spécifier un type pour vos variables, mais aussi de définir une valeur par défaut et d'influencer le comportement d'héritage à l'aide d'une API JavaScript. Techniquement, cela permettrait également aux propriétés personnalisées d'être animées avec des animations et des transitions CSS standards, ce qui est également pris en compte.

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

Métriques de police

Les métriques de police sont exactement ce à quoi cela ressemble. Quel est le cadre de délimitation (ou les cadres de délimitation) lorsque j'affiche la chaîne X avec une police Y à la taille Z ? Que se passe-t-il si je dois utiliser les annotations Ruby ? Cela a été très demandé, et Houdini devrait enfin réaliser ces vœux.

Et ce n'est pas tout !

La liste des brouillons de Houdini contient encore plus de spécifications, mais l'avenir de celles-ci est un peu incertain et ne sont pas plus que de simples espaces réservés pour des idées. Il peut s'agir, par exemple, de comportements de dépassement personnalisés, d'une API d'extension de syntaxe CSS, d'extension du comportement de défilement natif et d'éléments tout aussi ambitieux qui permettent des actions qui n'étaient pas possibles auparavant sur la plate-forme Web.

Démonstrations

J'ai mis à disposition en Open Source le code de la démo (démonstration en direct avec polyfill).