https://thenextweb.com/syndication/2020/05/31/7-strategies-to-design-landing-pages-that-convert-in-2020/
Attention les amis, vous n’allez pas en croire vos yeux, mais un simple fond d’écran à la faculté de faire crasher certains smartphones Android dont certains Samsung Galaxy comme le Note 9 ou le S20 Ultra ou encore des smartphones de chez Google comme le Pixel 3 XL. Le problème … Suite
https://dribbble.com/stories/2020/06/01/design-with-your-inner-child
https://www.fastcompany.com/90511044/google-search-will-now-favor-websites-with-great-ux
https://designshack.net/articles/trends/transparent-text-bold-background/
Pour la culture libre, le problème du financement est souvent crucial. Voici la proposition d’une sorte de petite bourse de « débutant sur PeerTube » qui nous fait plaisir et que nous vous invitons à considérer. Sourcehut qui fait cette offre est … Lire la suite
https://www.designweek.co.uk/issues/1-7-june-2020/furlough-freelance-scheme-change/
https://speckyboy.com/freelance-web-design-business-post-pandemic/
https://www.webdesignerdepot.com/2020/06/3-essential-design-trends-june-2020/
Nous parlons aujourd'hui de trois moyens pour évaluer les campagnes de netlinking. En utilisant les trois méthodes suivantes, vous pouvez mesurer et vous rendre compte du succès et de vos efforts en matière de création de liens.
Vous connaissez tous sans aucun doute, Audacity, le logiciel libre qui permet de retravailler tout ce qui est du domaine de l’audio. Mais pour un besoin encore plus ponctuel, sans rien installer, je vous invite à tester AudioMass, un éditeur audio 100% web que vous pouvez même installer chez vous … Suite
https://designxplorer.co/best-ui-interactions-of-the-month-may-2020/
Some modern UI animation shots to keep you up-to-date with the freshest trends.
The post UI Interactions & Animations Roundup #7 appeared first on Codrops.
Le secteur de la vente au détail a beaucoup souffert ces dernières semaines. Selon une étude MacKinsey, 44% des emplois du secteur du commerce de gros et de détail seraient menacés en Europe et ce, alors même qu'il est soumis à une énorme pression pour assurer un service le plus normal possible.
Avez-vous des pages zombies sur votre site ? Avec une performance SEO nulle ou très mauvaise, un contenu et un intérêt très faibles, elles plombent votre référencement. RM Tech les détecte, les classe et vous aide à savoir quoi en faire.
Nouvel épisode de notre émission hebdomadaire avec l’ami Rémi de DansTonChat ! Et cette semaine nous revenons avec un 3e Quizosaure histoire de jouer tous ensemble (comme environ chaque mois dorénavant). Et comme nous aimons bien tester de nouvelles choses régulièrement le jeu de cette semaine est différent des 2 … Suite
Un des enseignements majeurs de la crise que nous traversons, c'est que la distanciation sociale appelle la digitalisation ! Moins de contacts physiques implique plus de contacts dématérialisés. Pour les marques, le digital n'est pas seulement une opportunité mais un devoir dans le monde post-confiné.
Lorsque vous devez travailler sur des interfaces, le contraste des couleurs est une réalité dont vous devez tenir compte pour la rendre accessible. Vous avez le droit de craindre de perdre une partie de l’esthétique de votre interface, notamment si vous êtes habitué à un mauvais rapport de contraste. L’accessibilité a ses contraintes, mais finalement […]
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 :
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.
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.
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.
À 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.
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 workletregisterPaint()
pour réaliser le dessin au sein du worklet (dans un fichier séparé)paint()
pour utiliser le workletLe 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 :
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 dessingeom
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.
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.
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 workletregisterLayout()
pour construire les règles du positionnement dans le workletlayout()
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");
}
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.
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 :
--angle
pour une durée de 1 (avec une valeur de 0 à 1 tour complet)new ScrollTimeline
avec scrollSource: scrollElement
) et le « temps » équivalent est défini à 1CSS.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 :
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.
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 :
border-clip
, bordures multiples, etc.), ni des ombres, ni des images d’arrière-plan (répétition, position, taille, etc.)border-image
+ border-outset
), comme pour les ombresDans 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 ?).
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 :
corner-shape
présente dans les specs et supportée par aucun navigateur, réalisable avec l’API Paintsubgrid
du Grid Layout supporté dans Firefox, réalisable avec l’API Layoutfilter()
supportée dans Safari et réalisable avec l’API PaintCependant, pas de miracle, la grande majorité de CSS est non polyfillable1.
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.
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. Selon @gsnedders, c’est même pire, car 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.
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.
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 ?
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 ?
Si vous en doutez encore, lisez The Dark Side Of Polyfilling CSS ↩︎
Ça semble être une bonne pratique de conception pour les nouvelles fonctionnalités coté client ↩︎
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 :
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.
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.
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.
À 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.
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 workletregisterPaint()
pour réaliser le dessin au sein du worklet (dans un fichier séparé)paint()
pour utiliser le workletLe 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 :
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 dessingeom
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.
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.
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 workletregisterLayout()
pour construire les règles du positionnement dans le workletlayout()
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");
}
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.
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 :
--angle
pour une durée de 1 (avec une valeur de 0 à 1 tour complet)new ScrollTimeline
avec scrollSource: scrollElement
) et le « temps » équivalent est défini à 1CSS.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 :
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.
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 :
border-clip
, bordures multiples, etc.), ni des ombres, ni des images d’arrière-plan (répétition, position, taille, etc.)border-image
+ border-outset
), comme pour les ombresDans 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 ?).
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 :
corner-shape
présente dans les specs et supportée par aucun navigateur, réalisable avec l’API Paintsubgrid
du Grid Layout supporté dans Firefox, réalisable avec l’API Layoutfilter()
supportée dans Safari et réalisable avec l’API PaintCependant, pas de miracle, la grande majorité de CSS est non polyfillable1.
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.
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. Selon @gsnedders, c’est même pire, car 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.
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.
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 ?
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 ?
Si vous en doutez encore, lisez The Dark Side Of Polyfilling CSS ↩︎
Ça semble être une bonne pratique de conception pour les nouvelles fonctionnalités coté client ↩︎
Votre dose mensuelle d’inspiration web design est arrivée pour vous en mettre plein les yeux !...
https://www.smashingmagazine.com/2020/05/desktop-wallpaper-calendars-june-2020/
https://uxdesign.cc/10-examples-of-custom-404-pages-ranked-from-best-to-worst-9c74825c18c9
Après un long confinement ayant fragilisé leurs finances et leurs activités, le plus dur reste à venir pour les start-up. Beaucoup d'entre elles doivent repenser leur stratégie en faisant preuve d'inventivité.
Les CSS c’est parfois la prise de tête. Mais c’est quand même bien pratique ! La développeuse Diana Smith, connue également sous le pseudo de CyanHarlow s’est essayé à l’art via CSS ! Et qu’est ce que ça donne ? Et bien voilà quelques-unes de ses oeuvres. Et si vous … Suite
Sur mon site je vous ai déjà parlé de nombreux outils permettant de jouer et transformer vos fichiers vidéo ou audio. MystiQ Video Converter est un logiciel libre supplémentaire à mettre dans votre arsenal, et cela que vous soyez utilisateur de Windows (7 et plus), macOS (toujours en bêta) et … Suite
Comme chaque lundi, un coup d’œil dans le rétroviseur pour découvrir les informations que vous avez peut-être ratées la semaine dernière. Brave New World Covid-19 : les (très) bonnes recettes de la Nouvelle-Zélande pour maîtriser le virus – Le Parisien (leparisien.fr) … Lire la suite
Loki n’est pas uniquement le frangin de Thor (le Dieu). C’est également un réseau permettant à ses utilisateurs de communiquer ou de réaliser des transactions de manière privée, sécurisée et anonyme via Internet au travers d’un système décentralisé. Loki serait-il le frère de Tor (le réseau) ? Loki combine un … Suite