David Madore's WebLog: J'ai déjà dit que je haïssais le CSS ?

Index of all entries / Index de toutes les entréesXML (RSS 1.0) • Recent comments / Commentaires récents

Entry #2521 [older|newer] / Entrée #2521 [précédente|suivante]:

(mercredi)

J'ai déjà dit que je haïssais le CSS ?

(Ah oui, je l'ai déjà dit. Mais ça ne fera pas de mal de le redire. C'est donc parti pour un nouvel épisode de la saga Ruxor se plaint de combien le monde informatique est merdique et mal conçu.)

À chaque fois que je suis obligé de toucher à CSS, j'en ressors horrifié : aussi bien par la complexité de toutes ces règles byzantines que par le génie avec lequel ce langage semble toujours résoudre 90% de votre problème et vous narguer en rendant impossible de résoudre les 10% restants malgré son nombre invraisemblable de features mutantes.

Bon, la dernière fois que je m'en étais plaint, c'était à propos du scaling des images. Quand je mets une image dans mon blog j'écris typiquement une balise <img>, quelque chose comme :

<img src="blueish-foobar.png" width="400" height="300" alt="[Photo d'un foobar bleuté]" style="float: right; clear: right; margin-left: 1em; margin-bottom: 1em" />

(Enfin, le style= est en fait dans une classe CSS, mais peu importe.)

Dans cet exemple, le fichier PNG ferait 400×300. La raison de préciser la largeur et la hauteur de l'image dans les attributs est de permettre au navigateur de préparer la taille même si l'image ne se charge pas immédiatement, et de ne pas avoir à faire un reflow quand elle se charge (parce qu'il ne découvrirait la taille qu'à ce moment-là).

Je mets typiquement des images de taille de l'ordre de 400 pixels, et je fais normalement un lien vers une version plus grande. Éventuellement, si l'image est très grosse et vraiment centrale à la discussion, je vais la centrer au lieu de la flotter à gauche ou à droite. Enfin, peu importe.

Sur un mobile, ça ne marche pas du tout, parce que les pixels utilisés par CSS sont pipo (pour que le texte ne soit pas trop petit) et du coup l'image devient beaucoup trop grande par rapport aux lignes de texte. Quand je m'en suis plaint la dernière fois (dans cette entrée), je ne connaissais pas la solution. J'ai enfin fini par apprendre que (1) on peut mettre un max-width qui va overrider le width même si les deux existent (c'est utile mais très contre-intuitif), et (2) la valeur magique auto qui fait tout même le café (mais on ne comprend rien à ce qu'elle fait exactement) permet de demander que la hauteur de l'image soit ajustée pour rester proportionnelle à la largeur. D'où la solution :

<img src="blueish-foobar.png" width="400" height="300" alt="[Photo d'un foobar bleuté]" style="max-width: 60%; height: auto; float: right; clear: right; margin-left: 1em; margin-bottom: 1em" />

Comme ça l'image fait 400×300 (la taille du fichier, i.e., sa taille « naturelle »), sauf si ça va dépasser 60% de la largeur du contenant, auquel cas la largeur de l'image est limitée à cette valeur et sa hauteur est ajustée pour garder l'aspect ratio 4/3. C'est parfait.

Ajout : Comme un long commentaire me fait prendre conscience que je n'ai pas du tout été clair, j'explique mon intention : mon but est que l'image fasse sa taille naturelle (400×300), sauf si la fenêtre (ou l'écran du mobile) est tellement petit qu'on ne puisse pas mettre cette largeur de 400 pixels dans 60% de la taille disponible, auquel cas je veux limiter la taille de l'image à ces 60%. En effet, il s'agit d'images qui, typiquement, illustrent un paragraphe de texte, donc je veux laisser à côté de l'image un peu de place (les 40% restants, donc) pour que le texte s'inscrive à côté de l'image. (Laisser trop peu ou pas du tout de place pour le texte fait que l'image interrompt le texte, et ce n'est pas plaisant.) En revanche, dans le cas où je mets une image centrée (j'aurais sans doute dû prendre cet exemple-là, mais il est moins fréquent sur mon blog), je vais bien sur écrire max-width: 100% à la place.

C'est parfait, mais enfin, c'est extrêmement peu intuitif, moi j'aurais préféré une syntaxe beaucoup plus explicite du genre width: min(0.6*widthof(parent), 400px); height: (300/400)*widthof(this) (j'invente complètement, mais vous voyez l'idée), ce qui aurait précisé très clairement et très intuitivement ce que je veux, plutôt qu'un auto complètement magique dont on ne sait pas ce qu'il va faire (sur d'autres types d'élément, auto demande d'ajuster à la taille du contenu : quel est le rapport entre ces deux sens du mot ?).

Enfin, j'étais quand même vachement content d'apprendre ça. Mais maintenant il y a la complication du SVG. Parce que mes images sont parfois en SVG inlinées dans le HTML lui-même. Ce qui est censé être mieux (image vectorielle, pas de besoin de requête HTTP supplémentaire), mais présente aussi des inconvénients (Google Images ne semble pas assez malin pour récupérer les images SVG, par exemple, et a fortiori quand elles sont inlinées). Typiquement mon SVG ressemble à quelque chose comme ceci :

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="400" height="300" style="float: right; clear: right; margin-left: 1em; margin-bottom: 1em; stroke-linecap: round; stroke-linejoin: round"><!-- Insert vector of a blueish foobar *here* --></svg>

Si on fait pareil qu'avant, c'est-à-dire ajouter max-width: 60%; height: auto dans le style, ça ne va pas marcher, parce que l'élément <svg> va certes être redimensionné, mais l'image qu'il contient va simplement être coupée, pas redimensionnée. La solution est d'utiliser l'attribut viewBox qui définit un système de coordonnées dans le SVG, donc quelque chose comme viewBox="0 0 400 300" pour demander au navigateur que, que même si l'élément <svg> est redimensionné, les coordonnées à l'intérieur aillent quand même de 0 à 400 et de 0 à 300. On en arrive à ceci :

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 400 300" width="400" height="300" style="max-width: 60%; height: auto; float: right; clear: right; margin-left: 1em; margin-bottom: 1em; stroke-linecap: round; stroke-linejoin: round"><!-- Insert vector of a blueish foobar *here* --></svg>

Enfin, ça c'est par imitation de ce que je fais pour un <img> : ce n'est pas clair que ce soit la meilleure chose à faire : je pourrais retirer height="300" et/ou remplacer width="400" par un width:400px dans le style. Sur Firefox ça semble marcher correctement, et sur la version de Chrome que j'ai sous la main aussi. Malheureusement, cet article (qui est intéressant mais un peu confus dans sa formulation) met en garde que :

Many browsers—IE, Safari, and versions of Opera and Chrome released prior to summer 2014—will not auto-size inline SVG. If you don't specify both height and width, these browsers will apply their usual default sizes, which as mentioned previously will be different for different browsers. The image will scale to fit inside that height or width, again leaving extra whitespace around it. Again, there are also inconsistencies in what happens if you leave both height and width auto.

À la place, ils proposent un hack absolument ignoble faisant intervenir padding-bottom. Il est hors de question que j'utilise un truc pareil (j'ai déjà assez de mal avec le CSS et peur de tout casser dès que je change quelque chose, si je commence à utiliser des hacks pareils je n'y comprendrai vraiment plus rien ; et surtout, j'ai peur que ça soit très peu robuste). Mais il y a des choses qui restent tout à fait obscures pour moi :

Bon, ne sachant pas quoi en penser, je vais juste ignorer le problème et jouer moi aussi au petit jeu con de si ça marche sous Firefox et Chrome, c'est bon.

Mais ça m'amène à un problème lié : supposons que ce que je veux dimensionner n'est pas une image (<img> ni <svg>) mais un élément bloc différent, disons un <div> (pensez par exemple à celui qui contient des images dans cette entrée-ci). Mon <div> n'a pas d'intrinsic aspect ratio, mais supposons que je veux lui en spécifier un. Autrement dit, mon problème est :

Comment dimensionner un élément bloc, en CSS (sans JavaScript), en précisant sa largeur et son aspect ratio (ou bien sa hauteur et son aspect ratio) ?

Dans la syntaxe que j'ai pipoté plus haut, je pourrais vouloir écrire width: min(0.6*widthof(parent), 400px); height: (300/400)*widthof(this) exactement comme pour l'image : c'est-à-dire, imposer que la largeur soit le plus petit entre 400 pixels et 60% de la largeur de l'élément parent, et que le hauteur soit 3/4 de cette valeur. Ou dans une syntaxe plus proche du CSS réel, on pourrait imaginer d'écrire width:400px; max-width:60%; aspect-ratio:4/3; height: auto. Mais tout ça n'existe pas.

Dans le monde réel, est-ce possible, oui ou merde ? cette page semble penser que non, puisque toutes les réponses sont complètement pourries (la réponse approuvée propose le hack absolument ignoble à base de padding-bottom, qui d'ailleurs ne marcherait pas si c'était la hauteur qui était fixée et la largeur à en déduire ; l'unité vw est intéressante mais d'usage extrêmement limité ; d'autres hacks à base d'images sont tout aussi répugnants[#] que celui à base de padding-bottom ; et certains ne lisent pas la question et proposent du JavaScript). C'est tout de même hallucinant qu'un langage aussi complexe, malgré toutes les extensions comme calc() et les variables CSS (à quoi c'est censé servir, alors), ne permette toujours pas de faire quelque chose d'aussi simple (sans passer par un hack à vomir) !

Voilà, donc comme je le disais, un exemple parfait où le CSS résout 90% de votre problème et vous nargue en rendant impossible de résoudre les 10% restants malgré son nombre invraisemblable de features mutantes.

[#] En fait, parmi les différents hacks proposés, celui qui me semble finalement le moins ignoble est celui basé sur du SVG justement :

<div style="position: relative; width: 400px; max-width: 60%">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
    viewBox="0 0 4 3" style="width: 100%" />
<div style="position: absolute; top: 0; left: 0; bottom: 0; right: 0; overflow: auto">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit…</p>
</div>
</div>

Cela devrait faire à peu près ce que je demandais (fixer une largeur de <div> de 400px ou 60% du contenant, la plus petite des deux valeurs, tout en imposant un aspect ratio de 4:3), et on peut presque défendre l'idée que c'est une solution propre (en un certain sens, conceptuellement, on se sert de SVG pour « dessiner » un rectangle vide dans lequel on inscrit ensuite le texte ; en tout cas, c'est moins moche qu'avec le padding-bottom qui n'a rien à foutre là). Mais ce qui n'est pas clair, c'est sur quels navigateurs ça fonctionnera, ni à quel point la dégradation sera gracieuse si ça ne fonctionne pas. C'est justement mon problème initial sur le SVG.

Au passage : des choses que je ne sais pas dire en bon français : une feature, le scaling des images, l'aspect ratio, du SVG inliné dans du HTML, un hack. Je ferais sans doute mieux d'écrire ce genre d'entrées entièrement en anglais.

↑Entry #2521 [older|newer] / ↑Entrée #2521 [précédente|suivante]

Recent entries / Entrées récentesIndex of all entries / Index de toutes les entrées