Niveau Niveau confirmé

CSS Houdini

Articlecss

Publié par le , mis à jour le (31138 lectures)

css javascript animation performance Houdini

Avant de rentrer dans le vif du sujet, un peu de contexte.

En 2013, un collectif crée l’extensible web manifesto, en faveur d’un web extensible. L’objectif affiché est clair : réfléchir à une nouvelle forme de standards, qui laissent la liberté aux concepteurs de définir leurs propres fonctionnalités. Le but est donc de fournir des APIs plus bas niveau, un accès au cœur du navigateur, et ainsi inclure les concepteurs dans le processus d’innovation sans les restreindre aux seuls consensus définis par les standards historiques.

Dans l’univers HTML, on peut évoquer les composants web (web components) qui résultent de cette philosophie. Plusieurs standards ont été mis au point pour nous aider à créer nos propres composants HTML, et ainsi étendre le langage HTML. Cette solution étant bien entendu basée sur HTML, CSS et JavaScript.

Coté CSS, c’est justement l’ambition d’Houdini : de nouveaux standards pour concevoir nos propres effets graphiques, nos propres modes de positionnement, et pourquoi pas nos propres extensions (de nouveaux sélecteurs, de nouvelles propriétés, de nouvelles fonctions ,etc.), et bien plus encore. En un mot, étendre CSS à notre envie.

Concrètement, c’est nous donner accès à toutes les phases qui sont effectuées par un navigateur pour passer d’un fichier texte à un rendu écran. On peut décomposer sommairement les actions réalisées par les navigateurs de la sorte :

  • la première étape est le Parsing, le navigateur lit et « déchiffre » les documents HTML et CSS
  • le navigateur crée le DOM et le CSSOM, des représentations objets de ces fichiers textes
  • en découle le Render Tree, ou Layer Tree, une sorte de liste des styles à appliquer pour dessiner chaque élément de la page
  • enfin, le navigateur dessine les éléments en passant par 3 phases :
    • Layout, le navigateur applique les règles de positionnement (display, tailles, marges, etc.) et construit donc l’architecture. On parle aussi de reflow.
    • Paint, le navigateur applique les règles de dessin (arrières-plans, bordures, images de fond). On parle aussi de repaint.
    • Composite, une phase de compositing, c’est à dire de l’empilement des différents calques créés par certaines propriétés CSS (transformations, opacité, etc.). Souvent effectuée par la carte graphique et dans un thread séparé.

Actuellement, si l’on souhaite créer un effet visuel innovant pour une interface, il nous faut alors modifier le DOM. C’est la seule porte d’entrée au mécanisme interne des navigateurs.

Pipeline de rendu des navigateurs web, avec le DOM comme porte d’entrée

L’ambition de CSS Houdini, c’est de nous permettre d’accéder à toutes les étapes internes d’un navigateur, comme le montre l’image ci-dessous.

Pipeline de rendu des navigateurs web, avec toutes les étapes comme portes d’entrées (futur)

Pour cela, c’est donc bien tout un ensemble d’API (notamment JavaScript) qui sont en cours de standardisation.

Remarquez au passage que CSSOM (plutôt complexe et mal implémenté par les navigateurs) est plus ou moins remplacé par Typed OM. Ce standard plus robuste définit un ensemble de classes d’objets permettant de manipuler CSS (les différents fichiers, les at-rules, les sélecteurs, les déclarations, les propriétés, les valeurs, etc.).

Typed OM est par conséquent utile à tous les autres standards, son principal intérêt étant la structuration du code JS qui manipule CSS, comme par exemple pour en finir avec les concaténations hasardeuses :

// CSSOM
el.style.setProperty('transform', 'translate(' + x + 'px, ' + y + 'px)')
// Typed OM
el.attributeStyleMap.set('transform', new CSSTranslate(CSS.px(x), CSS.px(y)))

Ou bien tout simplement, pour récupérer les valeurs sous forme d’objet au lieu de chaine de caractères :

// CSSOM
getComputedStyle(el).getPropertyValue('width')      // '50px'
// Typed OM
el.computedStyleMap().get('width')                  // CSSUnitValue {value: 50, unit: 'px'}

Note : vous pouvez retrouver le support de CSS Houdini sur https://ishoudinireadyyet.com. Vous remarquez que Chrome est en tête sur l’implémentation, mais c’est légèrement enjolivé (et oui, le site est maintenu par les équipes de Google). Je préciserais plus en détails dans la suite de l’article. Par exemple, pour Typed OM, le support n’est effectif que pour une partie uniquement, mais il existe une liste.

Créer ses propres propriétés

À présent, listons quelques cas d’usages d’Houdini.

Depuis quelques années, il est déjà possible de créer ses propres propriétés CSS. C’est le standard des propriétés personnalisées (custom properties), que l’on connaît également sous le nom des variables CSS.

Prenons la propriété box-shadow. Si l’on souhaite changer l’une de ses valeurs, il est nécessaire de réécrire la règle entièrement, comme dans cet exemple où l’on modifie la valeur de flou au survol

.el {
  box-shadow: 0 3px 3px black;
}
.el:hover {
  box-shadow: 0 3px 10px black;
}

Grâce aux propriétés custom de CSS, on peut définir une propriété --box-shadow-blur pour cela et seule cette propriété pourra être modifiée. Elle est appliquée depuis l’état initial à l’aide la fonction var()

.el {
  --box-shadow-blur: 3px;
  box-shadow: 0 3px var(--box-shadow-blur) black;
}
.el:hover {
  --box-shadow-blur: 10px;
}

Cela s’avère pratique, mais dans ce cas précis, il est impossible d’animer cette nouvelle propriété. En effet, le navigateur n’a aucune connaissance du type de valeur attendue et ne sait donc pas comment faire.

C’est là où l’API Properties & Values de Houdini entre en jeu. Cette spécification définit la nouvelle at-rule @property (en CSS) ainsi que la méthode CSS.registerProperty() (en JS) qui permettent d’enregistrer une propriété personnalisée, et notamment en précisant le type CSS attendu. L’un des avantages est que le navigateur saura maintenant comment l’animer (si c’est possible). Reprenons le cas précédent, en ajoutant l’animation, et en déclarant notre nouvelle propriété

.el {
  --box-shadow-blur: 3px;
  box-shadow: 0 3px var(--box-shadow-blur) black;
  transition: --box-shadow-blur .45s;
}
.el:hover {
  --box-shadow-blur: 10px;
}

@property --box-shadow-blur {
  syntax: "<length>";
  inherits: false;
  initial-value: 0;
}

Et voilà, une belle animation au survol, qui ne modifie que la propriété souhaitée.

See the Pen CSS Houdini: Register a new property by Vincent De Oliveira (@iamvdo) on CodePen.

C’est une première étape pour étendre CSS: on demande au navigateur d’apprendre une nouvelle propriété, inconnue auparavant. Et les animations ne sont pas le seul intérêt à l’utilisation des propriétés customs. Cela peut également apporter un gain de performance, en précisant par exemple qu’une propriété custom ne s’hérite pas (ce qui évite notamment au navigateur d’appliquer des changements aux éléments enfants).

En passant, évitez d’appliquer trop de propriétés custom via le sélecteur :root, comme vous forcent certains plugins de l’univers de PostCSS par exemple. Des problèmes de performance sont à noter.

Le support est actuellement uniquement dans les navigateurs basés sur le moteur Blink (Chrome, Opera, Edge), et seulement la méthode JS. La nouvelle at-rule @property sera supportée très bientôt. Cependant, dans les 2 cas, tous les types ne sont pas encore implémentés (lié aussi à Typed OM), sans avoir de liste exhaustive.

Créer ses propres effets graphiques

Actuellement, les seuls effets graphiques réalisables sont ceux définis par le langage CSS. Des couleurs de fond, des bordures, des dégradés, des coins arrondis, des ombres, bref, vous connaissez tout ça.

Le futur standard CSS Paint API, comme son nom l’indique, nous donne accès à l’étape Paint des navigateurs. Ce standard définit un environnement d’exécution isolé (un worklet), dans lequel on peut dessiner programmatiquement une image, à l’instar de la balise <canvas> de HTML. Cette image peut ensuite être appliquée depuis les propriétés CSS qui les acceptent, principalement background-image, border-image et mask-image.

Ce nouveau standard définit donc :

  • CSS.paintWorklet.addModule('paint.js') pour charger un worklet
  • registerPaint() pour réaliser le dessin au sein du worklet (dans un fichier séparé)
  • la fonction CSS paint() pour utiliser le worklet

Le code d’un worklet est donc isolé du reste de la page, et n’est appelé que lors de la phase de Paint, ce qui rends le dessin plus performant, car le navigateur n’effectue plus toutes les étapes habituelles de mise à jour. De plus, les navigateurs peuvent facilement améliorer la performance de ce code spécifique (exécution dans un thread séparé notamment).

Prenons un effet graphique basique, mais pourtant pas si simple à réaliser en CSS : un élément dont le bord droit est incliné, comme sur cette image :

Effet de bord incliné que l’on souhaite réaliser

On devrait pouvoir s’en sortir avec un dégradé linéaire, ou encore des transformations CSS, mais difficile de gérer correctement le responsive (avec des tailles de polices différentes). Des éléments supplémentaires seront surement nécessaires.

Avec Houdini, cela devient un jeu d’enfant. Première étape, enregistrer le worklet, avec nos instructions de dessins, nommé slanted :

registerPaint('slanted', class {
  paint (ctx, geom) {
    ctx.fillStyle = 'hsl(296, 100%, 50%)';
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(geom.width, 0);
    ctx.lineTo(geom.width - 20, geom.height);
    ctx.lineTo(0, geom.height);
    ctx.fill();
  }
})

Sa méthode paint() est composée d’instructions de dessin qui crée la forme inclinée, et a accès à 2 variables :

  • ctx est le contexte de dessin
  • geom est un objet contenant la taille de l’élément où le dessin sera appliqué

Le dessin s’effectue donc à l’aide des instructions classiques du contexte de dessin lié à la balise <canvas> HTML : moveTo() pour se déplacer, lineTo() pour créer ligne droite, etc.

Ensuite, il nous faut charger ce worklet puis l’utiliser depuis notre CSS :

.el {
  background-image: paint(slanted);
}

Et voilà ! Le rendu est responsive par défaut, et redessiné automatiquement à chaque changement de taille de l’élément (essayez d’éditer le texte).

See the Pen CSS Paint API by Vincent De Oliveira (@iamvdo) on CodePen.

Là où ça devient vraiment intéressant, c’est lorsque l’on va récupérer les valeurs de propriétés custom dans le worklet, et que l’on utilise le tout avec des animations. Pour commencer, créeons un nouveau worklet, et dessinons un cercle qui s’adapte automatiquement à la plus petite largeur de notre élément :

// New worklet
registerPaint('circle', class {
  paint(ctx, geom, props) {
    // Get the center point and radius
    const x = geom.width / 2;
    const y = geom.height / 2;
    const radius = Math.min(x, y);

    // Draw the circle
    ctx.fillStyle = 'deeppink';
    ctx.beginPath();
    ctx.arc(x, y, radius, 0, 2 * Math.PI);
    ctx.fill();
  }
}

See the Pen CSS Paint API: Draw circle by Vincent De Oliveira (@iamvdo) on CodePen.

Continuons avec l’utilisation d’une propriété custom --circle-color définie en CSS et utilisée depuis le worklet, à l’aide du troisième argument de la méthode paint(), nommé props :

.el {
  --circle-color: deepskyblue;
  background-image: paint(circle);
}

@property --circle-color {
  syntax: "<color>";
  inherits: false;
  initial-value: currentcolor;
}
registerPaint('circle', class {
  static get inputProperties() { return ['--circle-color'] }
  paint(ctx, geom, props) {
    ...
    ctx.fillStyle = props.get('--circle-color').value;
    ...
  }
}

See the Pen CSS Paint API: Draw circle with custom props by Vincent De Oliveira (@iamvdo) on CodePen.

Dernière étape, créons trois nouvelles propriétés custom, --circle-x et --circle-y pour préciser le centre de notre cercle et --circle-radius pour sa taille. Ces trois propriétés sont utilisées dans le worklet

registerPaint('circle', class {
  static get inputProperties() { 
    return [ 
      '--circle-color', '--circle-radius', '--circle-x', '--circle-y'
    ]
  }
  paint(ctx, geom, props) {
    const x = props.get('--circle-x').value;
    const y = props.get('--circle-y').value;
    const radius = props.get('--circle-radius').value;
  }
}

À l’état initial, le cercle a une taille à 0, et cette propriété sera animable en CSS.

.el {
  --circle-radius: 0;
  --circle-color: deepskyblue;
  background-image: paint(circle-ripple);
}
.el.animating {
  transition: --circle-radius 1s,
              --circle-color 1s;
  --circle-radius: 300;
  --circle-color: transparent;
}

Et enfin, le centre est défini en JS à l’endroit où l’utilisateur clique sur l’élément. L’ajout de classe permet d’animer la taille du cercle.

el.addEventListener('click', e => {
  el.classList.add('animating');
  el.attributeStyleMap.set('--circle-x', e.offsetX);
  el.attributeStyleMap.set('--circle-y', e.offsetY);
});

See the Pen CSS Paint API: Animations by Vincent De Oliveira (@iamvdo) on CodePen.

Boom ! Le fameux effet ripple de Google Material en quelques lignes de code. Et le tout, de manière performante.

Grâce à ce type de worklet, on peut envisager pas mal d’effets nouveaux, ou tout du moins se simplifier la création de certains effets courants. Parmi mes expérimentations, vous pourrez notamment retrouver la création de flèche d’infobulles, d’une superellipse (des coins arrondis à la mode iOS), des bordures qui simulent un trait de crayon ou des surlignages type Stabilo, des dégradés des coins, ou pourquoi pas une grille irrégulière aléatoire si l’on mixe cette technique avec les masques CSS.

Mosaique des effets disponibles sur css-houdini.rocks

Le support de CSS Paint API est uniquement dans les navigateurs basés sur le moteur Blink. Et pas à 100% : les attributs de la fonction CSS paint() ne sont pas pris en charge notamment. Le fait de passer des attributs permet par exemple d'appeler le même worklet plusieurs fois sur le même élément, et ainsi obtenir des résultats différents, comme c’est le cas dans cet exemple de bordures « internes ».

De plus, les APIs de Houdini sont étroitement liées les unes aux autres. Par exemple, pour récupérer une propriété custom dans un worklet et l’utiliser sour forme d’objet, il faut que les navigateurs implémentent l’API Properties & Values (pour enregistrer le type de la propriété), mais également Typed OM. Même Chrome a une implémentation imprévisible. Beaucoup de tests sont nécessaires pour savoir ce qui est supporté ou non.

Créer ses propres modes de positionnement

Dans la même veine, il existe un worklet spécifique à la création de son propre mode de positionnement. C’est ce que définit le standard CSS Layout API.

À la manière de Flexbox et Grid, vous pouvez donc écrire votre propre moteur de placement d’éléments au sein d’un élément parent. Comment ? Et bien, comme pour CSS Paint API :

  • CSS.layoutWorklet.addModule('layout.js') pour charger un worklet
  • registerLayout() pour construire les règles du positionnement dans le worklet
  • la fonction CSS layout() pour utiliser le worklet avec la propriété display

Bien que Flexbox et Grid ouvrent beaucoup de possibilités, il y a encore certains layout non réalisable en CSS. L’un des plus populaires est le layout Masonry. Avec cette nouvelle API, cela devient possible, avec environ 40 lignes de JS :

// Code from https://github.com/GoogleChromeLabs/houdini-samples/blob/master/layout-worklet/masonry/masonry.js 
registerLayout('masonry', class {
  async layout(children, edges, constraints, styleMap) {
    const inlineSize = constraints.fixedInlineSize;

    let columns = Math.ceil(inlineSize / 350);
    let padding = 10;

    // Layout all children with simply their column size.
    const childInlineSize = (inlineSize - ((columns + 1) * padding)) / columns;
    const childFragments = await Promise.all(children.map((child) => {
      return child.layoutNextFragment({fixedInlineSize: childInlineSize});
    }));

    let autoBlockSize = 0;
    const columnOffsets = Array(columns).fill(0);
    for (let childFragment of childFragments) {
      // Select the column with the least amount of stuff in it.
      const min = columnOffsets.reduce((acc, val, idx) => {
        if (!acc || val < acc.val) {
          return {idx, val};
        }

        return acc;
      }, {val: +Infinity, idx: -1});

      childFragment.inlineOffset = padding + (childInlineSize + padding) * min.idx;
      childFragment.blockOffset = padding + min.val;

      columnOffsets[min.idx] = childFragment.blockOffset + childFragment.blockSize;
      autoBlockSize = Math.max(autoBlockSize, columnOffsets[min.idx] + padding);
    }

    return {autoBlockSize, childFragments};
  }
});

Puis, coté CSS

.el {
  display: layout(masonry);
}

Pour voir le résultat, chargez le CodePen suivant, dans un navigateur basé sur Blink, avec le flag Web Platform actif

See the Pen pojPXKx by Vincent De Oliveira (@iamvdo) on CodePen.

Certes, le code JS parait complexe, mais pas tant que ça au final. Et surtout, ce code est isolé du reste de la page, et n’est appelé que lors de la phase de Layout, ce qui le rends plus performant, comme expliqué précédemment.

Bien entendu, on peut donc envisager d’autres modes de positionnement, comme ceux utilisés pour le développement d’applications natives (iOS, Android). Les développeurs de Google ont par exemple écrit un worklet pour porter le RelativeLayout d’Android. On peut également être plus créatifs, et créer un mode où les éléments sont positionnés le long d’un chemin SVG, défini par une propriété custom :

.el {
  display: layout(svg-path);
  --path: path("M100,300c100,-100,150,-120,300,0c150,50,300,0,400,-200");
}
Les éléments HTML sont positionnés le long d’un chemin SVG (une vague)

Dans ce cas précis, cela nous évite des positionnements absolus à la louche et difficilement responsive. Certes, il est possible d’obtenir un résultat équivalent avec le module CSS Motion (pas Houdini) et la propriété offset, mais le tracé SVG n’est pas adaptatif par défaut (donc du JS est nécessaire) et le CSS doit prévoir à l’avance le nombre d’éléments à positionner.

Le support actuel de CSS Layout API est assez limité, car uniquement dans les navigateurs basés sur le moteur Blink avec le flag Web Platform. Ce ne sont que les premières implémentations.

Encore plus ?

Il existe un dernier type de worklet au sein d’Houdini, dédié à la performance des animations, c’est l’Animation Worklet API, basée sur WAAPI (Web Animations API). Comme pour les autres worklets, le code de l’animation est donc isolé, mais surtout, il autorise une baseline basée sur d’autres paramètres que le temps. C’est notamment utile pour obtenir des animations performantes basées sur les interactions utilisateurs, comme le scroll (manuel, mais aussi animé) :

Prenons un exemple, un worklet qui enregistre une simple animation linéaire 1 pour 1

registerAnimator('simple', class {
  animate(currentTime, effect) {
    effect.localTime = currentTime;
  }
});

Ce worklet est chargé, puis une animation est créée en JS :

  • elle met à jour une propriété custom CSS --angle pour une durée de 1 (avec une valeur de 0 à 1 tour complet)
  • elle est basée sur le scroll (new ScrollTimeline avec scrollSource: scrollElement) et le « temps » équivalent est défini à 1
CSS.animationWorklet.addModule('...').then(r => {
  new WorkletAnimation('simple',
    new KeyframeEffect(el, [
        { '--angle': 0 },
        { '--angle': '1turn' }
      ],
      { duration: 1 }
    ),
    new ScrollTimeline({
      scrollSource: scrollElement,
      timeRange: 1
    }),
  ).play();
});

Finalement, la propriété --angle est utilisée en CSS pour pivoter l’intégralité d’un cube en 3D

.cube {
  --angle: 0;
  transform: rotateX(var(--angle)) rotateZ(45deg) rotateY(-45deg);
}

Pour voir le résultat, chargez le CodePen suivant, dans un navigateur basé sur Blink, avec le flag Web Platform actif

See the Pen CSS Houdini Animation API: Scroll by Vincent De Oliveira (@iamvdo) on CodePen.

Le support est actuellement limité aux navigateurs basés sur le moteur Blink avec le flag Web Platform.

L’ambition de CSS Houdini, c’est d’aller encore plus loin. Rien n’existe encore vraiment à ce stade, mais on peut citer :

  • l’API CSS Parser pour avoir accès à la première étape des navigateurs : la lecture du fichier. J’imagine donc qu’il serait possible de créer ses propres fonctions, ses propres sélecteurs, etc. puisque l’on pourrait les parser nous-mêmes. Reste à savoir ce que l’on pourrait en faire.
  • l’API Font Metrics pour accéder aux métriques des fontes en CSS. Et ça serait super pour éviter les petits désagréments actuels.

Mais alors, véritable magie ou simple poudre aux yeux ?

On peut être enthousiaste à l’idée de toutes ces nouveautés offertes par Houdini. Mais il faut quand même prendre en considérations quelques points.

Nouvelles possibilités offertes

Toutes ces nouvelles API augmentent la créativité et aide à mettre en place des effets qui étaient jusque là impossibles ou alors très compliqués à réaliser.

Comme vu plus haut, on peut spécifier ses propres propriétés, mais malheureusement on ne peut pas vraiment étendre des fonctionnalités existantes. Dans le cas de création d’une propriété pour le flou d’une ombre, il est par exemple impossible de créer un flou directionnel en divisant --box-shadow-blur en deux sous-propriétés, --box-shadow-blur-x et --box-shadow-blur-y. Il n’existe pas de solution pour «hacker» le dessin des ombres du navigateur.

Même si l’API Paint paraît révolutionnaire en soit, ce n’est finalement qu’une version performante de -webkit-canvas() qui existe depuis 2008, mais qui est maintenant retirée de Chrome.

Le dessin est effectué dans un canvas, via son contexte de type CanvasRendering2D (et encore, une version plus limitée). Il existe des centaines d’effets impossibles aujourd’hui, qui ne pourront pas être réalisés avec CSS Houdini. Ce contexte de dessin n’est pas initialement prévu pour CSS et il a donc de nombreuses contraintes :

  • pas de gestion simple des bordures (border-clip, bordures multiples, etc.), ni des ombres, ni des images d’arrière-plan (répétition, position, taille, etc.)
  • pas pratique de dessiner en dehors d’un élément (mais possible en combinant border-image + border-outset), comme pour les ombres
  • pas de gestion des textes
  • rien de nouveau pour styler les formulaires
  • etc.

Dans beaucoup de cas, SVG est un choix bien plus simple et pratique.

Concernant l’API Layout, ce sont uniquement des modes de positionnement complets qui sont réalisables (comme Flexbox ou Grid). C’est déjà très bien me direz-vous, mais cela ne permet pas de modifier la façon dont CSS fonctionne.

Impossible donc d’agir sur la taille, ou les marges, d’un seul élément, de changer son containing block (dans le cas d’un élément en position absolue) ou son contexte d’empilement (notamment en cas de conflit avec certaines propriétés), ni même d’ajouter de nouveaux pseudo-éléments ou d’autres entités (mais c’est peut être du ressort des web components ?).

Polyfill

Un des critères avancé est la possibilité de créer ses propres polyfills (combler le manque de support d’un navigateur par son propre code). C’est vrai, Houdini peut aider, mais il ne faut pas oublier que les navigateurs supportant Houdini mais ne supportant pas telle ou telle fonctionnalité, sont plutôt rares. Il existe pourtant quelques contre-exemples :

Cependant, pas de miracle, la grande majorité de CSS est non polyfillable1.

Performance

C’est le cheval de bataille de CSS Houdini : la performance de rendu. Et c’est tout à fait légitime. Aujourd’hui, en 2020, on est encore restreints dans la création d’effets visuels, et surtout lorsqu’ils sont animés. Les propriétés CSS de layout (width, height, margin, left etc.) ou mêmes des propriétés de dessin (background-color, background-size, etc.) sont très coûteuses en temps de rendu. C’est pour cela que les propriétés transform et opacity sont préférées, car sont traitées pendant la phase de compositing des navigateurs, et souvent effectuées par un thread séparé.

Regardez par exemple comment on peut animer une ombre portée (box-shadow) sans réellement animer l’ombre portée (spoiler: on anime l’opacité d’un pseudo-élément qui a l’ombre portée).

L’utilisation des worklets, isolés du reste de la page et du fameux thread principal2, permet donc d’obtenir des résultats performants, sans recourir exclusivement aux propriétés transform/opacity.

Concernant le worklet Animation API, je ne suis personnellement pas un grand fan de cette solution. Le standard WAAPI est à mon sens bien suffisant pour réaliser des animations performantes, et pour gérer facilement les transitions/animations CSS. Pour la partie animation au scroll, je préfère nettement la spécification Scroll-linked Animations et notamment la propriété animation-timeline et l’at-rule @scroll-timeline, mais qui ne font pas partie de Houdini.

Innovation des moteurs de rendu

On ne peut pas parler de performance de rendu, sans évoquer les moteurs de rendu. Actuellement il existe 3 moteurs principaux de navigateur : Blink qui alimente Chrome, Opera, Edge, etc., WebKit pour Safari, et Gecko pour Firefox.

Les APIs de CSS Houdini sont basées sur un consensus de rendu, qui est plus ou moins celui de chaque navigateur, mais on se doit d’évoquer le nouveau moteur de rendu de Firefox : WebRender. Ce nouveau composant a l’ambition de modifier fondamentalement les techniques de rendu : dorénavant les phases Paint et Compositing sont fusionnées et c’est le GPU qui se charge de l’intégralité du rendu, à la manière des jeux vidéos. C’est encore récent, mais quand ce sera en place, les techniques à base de transform/opacity seront obsolètes pour ce navigateur. Et selon @gsnedders, les APIs Houdini qui sont une réponse aux problèmes de performance de rendu dans le contexte actuel, seraient complexes à exécuter dans un contexte différent.

Et ça, c’est problématique, soit pour l’innovation, soit pour Houdini.

Tout en JavaScript

On peut également regretter que seules des APIs JavaScript soient en cours de standardisation. CSS Houdini, ce n’est finalement que du JS-in-CSS. Pas de JS, pas de styles.

Personnellement, j’aurais bien aimé pouvoir utiliser SVG dans un worklet. Le déclaratif, ça a du bon quelque fois. Mais pour être performant, il faudrait déjà que Blink/WebKit l’accélèrent matériellement. C’est pour bientôt dans WebKit.

Quoi qu’il en soit, ce qu’il en ressort est un code assez complexe à écrire et à mettre en oeuvre. Mais surtout, c’est souvent bien plus difficile que du JS « classique », via le DOM.

Sans rentrer dans des détails trop techniques, les worklets sont des environnements autonomes, qui ne doivent pas gérer d’état. Pour être sûr de cela, le navigateur doit créer deux instances pour chaque worklets, et utiliser indifféremment l’un ou l’autre pour le rendu. Cela complique énormément des effets qui semblent pourtant simple, comme dans cet exemple de bordures où chaque repaint crée des bordures différentes. Je me suis déjà cassé les dents plusieurs fois sur des effets à cause de cela. Il existe des solutions pour contourner ce problème, mais encore une fois, ça complexifie le code et crée souvent des effets de bords.

Plus basiquement, il ne faut pas sous-estimer le temps de chargement des scripts JS, voire tout simplement de leur non présence. Mais également du support de Houdini. Actuellement, les styles appliqués via paint() et layout() provoquent des FOUC (Flash of Unstyled Content).

La notion d’enrichissement progressif est plus que jamais d’actualité. Mais sera plus complexe à mettre en œuvre.

Sécurité

Enfin, dès que l’on ouvre un peu plus le coeur des navigateurs, s’en suivent des considérations de sécurité. Le principal frein est que l’utilisation des worklets ne peut se faire que sur un site en HTTPS. Pas de site sécurisé, pas de CSS. Et c’est un peu regrettable3.

Malgré cela, des chercheurs sont tout de même parvenus à exploiter une faille, qui permet de récupérer assez facilement un historique de navigation. La solution de contournement prise par les équipes de Chrome a été d’interdire l’utilisation de la fonction paint() sur les liens HTML. Là encore, c’est à mon avis une très grosse contrainte qui va en limiter l’adoption si cela en reste là.

Et surtout, combien de temps encore pour que d’autres failles soient découvertes ? Est-ce que l’avenir de CSS Houdini est lié à celui des CSS Shaders (des filtres CSS customs qui permettaient d’appliquer des shaders WebGL à la volée) qui ont disparus du jour au lendemain des navigateurs qui les avaient implémentés ?

Conclusion

Cette nouvelle façon de concevoir des standards est intéressante. Elle ouvre la porte aux concepteurs, et permet de les inclure dans le processus d’innovation. Avec CSS Houdini, de nouveaux effets sont possibles, et ces effets sont rendus de manière plus performante dans les navigateurs actuels. Mais cela implique quand même quelques contraintes : du JavaScript supplémentaire, plus complexe à mettre en oeuvre, etc.

Dans tous les cas, CSS Houdini est surtout pensé pour la performance, pas vraiment pour la créativité.

On peut aussi voir ces APIs comme une opportunité pour l’évolution des standards classiques. Si un effet visuel, ou un mode de positionnement, devient populaire, il pourrait alors être standardisé, pour être inclus directement en CSS. Mais qu’en sera t’il de la gestion des performances si l’on conserve les méthodes de rendus actuelles ?

Alors, vous, que pensez-vous de tout ça ?

Commentaires

Intéressant sur le papier... mais dès l'instant où cela requiert JavaScript je ne chercherai même pas à approfondir la technique.

Il me semble qu'on assiste à une débauche d'effets visuels tous plus bling bling les uns que les autres, sans que cela soit toujours justifié. A titre personnel je préfère nettement une page plus simple et sans fioritures qui se charge vite et m'évite de perdre quelques dixièmes supplémentaires de ma vue, déjà bien malmenée.

Plus généralement, avancer pléthore de nouvelles technologies avec des API plus ou moins bien supportées d'un navigateur à l'autre me paraît de nature à voir réapparaître le syndrome "optimisé pour..." que nous avons connu par le passé. Penser que tous ces navigateurs fourniront le même rendu et reconnaîtront les mêmes API de façon identique est illusoire. A moins que des trois principaux moteurs de rendu existant actuellement il n'en reste plus qu'un à terme... ce que, à mon sens, les gens de Google aimeraient bien voir arriver, auquel cas le problème ne se poserait plus mais au détriment de la diversité, l'heureux élu imposant de facto ses choix au reste de l'Humanité (rien que ça).

Bref, de nouvelles briques sur lesquelles garder un oeil mais qu'il ne me semble pas urgent de voir arriver. Se préoccuper d'accessibilité réelle au niveau des pages web me paraît dans l'immédiat beancoup plus souhaitable, plutôt que de tenter une fuite en avant en matière d'effets visuels.

Commenter

Vous devez être inscrit et identifié pour utiliser cette fonction.

Connectez-vous (déjà inscrit)

Oubli de mot de passe ? Pas de panique, on va le retrouver

Pas encore inscrit ? C'est très simple et gratuit.