Houdini – Demystifying CSS

Vous êtes-vous déjà demandé la quantité de travail que le CSS effectue ? Vous modifiez un seul attribut, et soudainement, l'ensemble de votre site Web s'affiche dans une mise en page différente. C'est un peu magique. Jusqu'à présent, nous, la communauté des développeurs Web, n'avons pu que constater et observer la magie. Et si nous voulions créer notre propre magie ? Et si nous voulions devenir le magicien ?

Découvrez Houdini !

La task force Houdini est composée d'ingénieurs de Mozilla, Apple, Opera, Microsoft, HP, Intel et Google qui travaillent ensemble pour exposer certaines parties du moteur CSS aux développeurs Web. Le groupe de travail travaille sur une collection de projets dans le but de les faire accepter par le W3C pour qu'ils deviennent des normes Web. Ils se sont fixés quelques objectifs généraux, les ont transformés en ébauches de spécifications, qui ont à leur tour donné naissance à un ensemble d'ébauches de spécifications de niveau inférieur.

C'est généralement l'ensemble de ces versions qui est désigné par le terme "Houdini". Au moment de la rédaction de cet article, la liste des brouillons est incomplète et certains d'entre eux ne sont que des espaces réservés.

Les spécifications

Worklets (spécification)

Les worklets ne sont pas vraiment utiles en eux-mêmes. Il s'agit d'un concept introduit pour rendre possibles de nombreuses versions ultérieures. Si vous avez pensé aux Web Workers lorsque vous avez lu "worklet", vous n'avez pas tort. Ils présentent de nombreux chevauchements conceptuels. Pourquoi créer un nouveau concept alors que nous avons déjà des travailleurs ?

L'objectif de Houdini est de présenter 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 l'être par définition. D'après la spécification du nœud de calcul Web:

Cela signifie que les nœuds de travail Web ne sont pas adaptés à ce que Houdini prévoit de faire. C'est pourquoi les worklets ont été inventés. Les worklets utilisent des classes ES2015 pour définir un ensemble de méthodes dont les signatures sont prédéfinies par le type du worklet. Ils sont légers et de courte durée.

API CSS Paint (spécification)

L'API Paint est activée par défaut dans Chrome 65. Consultez la présentation détaillée.

Worklet de composition

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

Même si la spécification du worklet de composition a été déplacée vers le WICG et qu'elle sera itérée, c'est celle qui m'enthousiasme le plus. Certaines opérations sont sous-traitées à la carte graphique de votre ordinateur par le moteur CSS, bien que cela dépende à la fois de votre carte graphique et de votre appareil en général.

Un navigateur prend généralement l'arborescence DOM et, en fonction de critères spécifiques, décide d'attribuer une couche à certaines branches et sous-arbres. Ces sous-arbres se peignent dessus (peut-être à l'aide d'un worklet de peinture à l'avenir). Pour finir, toutes ces couches individuelles, maintenant peintes, sont empilées et positionnées les unes sur les autres, en respectant les indices Z, les transformations 3D, etc., pour obtenir l'image finale visible à l'écran. Ce processus est appelé composition et est exécuté par le moteur de composition.

L'avantage du processus de composition est que vous n'avez pas besoin de faire repeindre tous les éléments lorsque la page défile un peu. À la place, vous pouvez réutiliser les calques du frame précédent et simplement réexécuter le compositeur avec la position de défilement mise à jour. Cela permet d'accélérer les choses. Cela nous permet d'atteindre 60 FPS.

Worklet de composition.

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

Pour être un peu plus précis, vous pouvez indiquer au navigateur que vous souhaitez vous associer 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 à être placé sur son propre calque et à chaque frame, votre code est appelé. Vous pouvez déplacer votre calque en manipulant la transformation des calques et en modifiant ses attributs (comme opacity), ce qui vous permet de faire des choses sophistiquées à 60 FPS.

Voici une implémentation complète du défilement en 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 Flack a écrit un polyfill pour le worklet de composition afin que vous puissiez l'essayer, bien entendu avec un impact sur les performances beaucoup plus important.

Worklet de mise en page (spécification)

Le premier projet de spécifications a été proposé. L'implémentation est encore loin.

Encore une fois, les spécifications sont pratiquement vides, mais le concept est intéressant: écrivez votre propre mise en page ! Le worklet de mise en page est censé vous permettre d'effectuer 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 entendu, exécuter une implémentation JavaScript complète de la mise en page flex-box du CSS est plus lent que d'exécuter une implémentation native équivalente, mais il est facile d'imaginer un scénario où les compromis peuvent entraîner un gain de performances. Imaginez un site Web composé uniquement de cartes, comme Windows 10 ou une mise en page de style maçonnerie. Le positionnement absolu et fixe n'est pas utilisé, pas plus que z-index. Les éléments ne se chevauchent jamais et ne comportent aucun type de bordure ni de débordement. La possibilité d'ignorer toutes ces vérifications lors de la redisposition peut entraîner un gain de 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
        };
    }
});

CSSOM typé (spécification)

Le CSSOM (CSS Object Model ou modèle d'objet CSS) résout un problème que nous avons probablement tous rencontré et que nous avons appris à accepter. Je vais vous montrer un exemple avec une ligne de code JavaScript:

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

Nous effectuons des calculs, en convertissant un nombre en chaîne pour ajouter une unité afin que le navigateur analyse cette chaîne et la convertisse à nouveau en nombre pour le moteur CSS. Cela devient encore plus laid lorsque vous manipulatez des transformations avec JavaScript. Plus maintenant ! Le CSS est sur le point de se mettre à écrire.

Ce projet est l'un des plus aboutis, et un polyfill est déjà en cours de développement. (Clause de non-responsabilité: l'utilisation du polyfill ajoutera é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 le StylePropertyMap d'un élément, où chaque attribut CSS a sa propre clé et son 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. Définir height: calc(5px + 5%) génère un LengthValue{px: 5, percent: 5}. Certaines propriétés telles que box-sizing n'acceptent que certains mots clés et ont 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

(spécifications)

Connaissez-vous les propriétés personnalisées CSS (ou leur alias non officiel "variables CSS") ? Voici les mêmes, 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. Cette ébauche 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 d'animer les propriétés personnalisées avec des transitions et des animations CSS standards, ce qui est également à l'étude.

["--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 elles ressemblent. Quel est le cadre de délimitation (ou les cadres de délimitation) lorsque j'affiche la chaîne X avec la police Y à la taille Z ? Que se passe-t-il si j'utilise des annotations Ruby ? Cette fonctionnalité a été très demandée, et Houdini devrait enfin la proposer.

Et ce n'est pas tout !

La liste des ébauches de Houdini contient encore plus de spécifications, mais leur avenir est plutôt incertain et elles ne sont guère plus que des espaces réservés pour les idées. Par exemple, les comportements de débordement personnalisés, l'API d'extension de la syntaxe CSS, l'extension du comportement de défilement natif et d'autres éléments tout aussi ambitieux, qui permettent d'effectuer des actions sur la plate-forme Web qui n'étaient pas possibles auparavant.

Démonstrations

J'ai rendu le code de la démonstration Open Source (démonstration en direct à l'aide d'un polyfill).