Syntaxes descriptives

Dans ce module, vous allez apprendre à proposer au navigateur une sélection d'images afin qu'il puisse prendre les meilleures décisions concernant les images à afficher. srcset n'est pas une méthode permettant d'échanger des sources d'image à des points d'arrêt spécifiques. Elle n'est pas destinée à remplacer une image par une autre. Ces syntaxes permettent au navigateur de résoudre un problème très difficile, indépendamment de nous: demander et afficher de manière transparente une source d'image adaptée au contexte de navigation de l'utilisateur, y compris la taille de la fenêtre d'affichage, la densité d'affichage, les préférences utilisateur, la bande passante et d'innombrables autres facteurs.

C'est une question de taille. Bien plus que nous ne souhaitons prendre en compte le simple balisage d'une image pour le Web, et bien faire cela implique plus d'informations qu'il n'est possible d'y accéder.

Décrire la densité avec x

Un élément <img> de largeur fixe occupera la même portion de la fenêtre d'affichage dans n'importe quel contexte de navigation, quelle que soit la densité de l'écran de l'utilisateur, c'est-à-dire le nombre de pixels physiques composant son écran. Par exemple, une image avec une largeur inhérente de 400px occupera la quasi-totalité de la fenêtre d'affichage du navigateur, à la fois sur le Google Pixel d'origine et sur le Pixel 6 Pro, qui est beaucoup plus récent. Les deux appareils ont une largeur d'affichage 412px logique en pixels normalisée.

Le Pixel 6 Pro dispose toutefois d'un écran beaucoup plus net: le 6 Pro a une résolution physique de 1 440 × 3 120 pixels, tandis que le Pixel est de 1 080 × 1 920 pixels, c'est-à-dire le nombre de pixels matériels qui composent l'écran lui-même.

Le ratio entre les pixels logiques d'un appareil et les pixels physiques correspond au rapport de pixels de l'appareil pour cet écran (DPR). La DPR est calculée en divisant la résolution d'écran réelle de l'appareil par les pixels CSS d'une fenêtre d'affichage.

DPR de 2 affiché dans une fenêtre de console.

Ainsi, le DPR du Pixel d'origine est de 2,6, tandis que celui du Pixel 6 Pro est de 3,5.

L'iPhone 4, le premier appareil dont le DPR est supérieur à 1, enregistre un ratio de pixels de 2. La résolution physique de l'écran est le double de la résolution logique. Tous les appareils antérieurs à l'iPhone 4 avaient un DPR de 1: un pixel logique pour un pixel physique.

Si vous affichez cette image de largeur 400px sur un écran avec un DPR de 2, chaque pixel logique est affiché sur quatre pixels physiques de l'écran: deux horizontaux et deux verticaux. L'image ne bénéficie pas de l'affichage haute densité. Son apparence sera identique à celle d'un écran avec un écran haute densité de 1. Bien entendu, tout ce qui est "dessiné" par le moteur de rendu du navigateur (texte, formes CSS ou SVG, par exemple) sera dessiné pour s'adapter à la densité d'affichage plus élevée. Toutefois, comme vous l'avez appris dans les formats d'image et la compression, les images matricielles sont des grilles fixes de pixels. Bien qu'elle ne soit pas toujours évidente, une image matricielle améliorée pour s'adapter à une densité d'affichage plus élevée s'affiche en basse résolution par rapport à la page environnante.

Pour éviter cette augmentation, l'image en cours de rendu doit avoir une largeur intrinsèque d'au moins 800 pixels. Lorsqu'elle est réduite pour s'adapter à un espace dans une mise en page de 400 pixels de largeur logique, la source d'image de 800 pixels présente une densité de pixels deux fois plus élevée. Sur un écran avec un RAD de 2, le rendu est beau et net.

Gros plan sur un pétale de fleur présentant une disparité de densité.

Étant donné qu'un écran avec un DPR de 1 ne peut pas utiliser la densité accrue d'une image, il sera réduit pour correspondre à l'écran. Et comme vous le savez, une image réduite s'affichera très bien. Sur un écran basse densité, une image adaptée à un affichage plus dense ressemble à n'importe quelle autre image basse densité.

Comme vous l'avez appris dans la section Images et performances, un utilisateur disposant d'un écran basse densité affichant une source d'image réduite à 400px n'aura besoin que d'une source dont la largeur inhérente est 400px. Bien qu'une image beaucoup plus grande conviendrait visuellement à tous les utilisateurs, une source d'image haute résolution haute résolution affichée sur un petit écran basse densité ressemble à n'importe quelle autre image basse densité, mais semble beaucoup plus lente.

Comme vous pouvez le deviner, les appareils mobiles avec un DPR de 1 sont très rares, bien qu'ils restent courants dans les contextes de navigation sur ordinateur. D'après les données partagées par Matt Hobbs, environ 18% des sessions de navigation GOV.UK enregistrées en novembre 2022 indiquent une DPR de 1. Bien que les images haute densité s'affichent telles que les utilisateurs pourraient s'y attendre, elles seront soumises à des coûts de traitement et de bande passante beaucoup plus élevés. Ceci est particulièrement préoccupant pour les utilisateurs d'appareils plus anciens et moins puissants, susceptibles de disposer d'un écran basse densité.

L'utilisation de srcset garantit que seuls les appareils dotés d'un écran haute résolution reçoivent des sources d'images suffisamment grandes pour un rendu net, sans transmettre ce même coût de bande passante aux utilisateurs disposant d'écrans de résolution inférieure.

L'attribut srcset identifie un ou plusieurs candidats séparés par une virgule pour le rendu d'une image. Chaque candidat est composé de deux éléments: une URL, comme vous le feriez dans src, et une syntaxe qui décrit la source de l'image. Chaque candidat dans srcset est décrit par sa largeur ("syntaxe w") ou sa densité prévue ("syntaxe x").

La syntaxe x est un raccourci pour "cette source convient à un affichage de cette densité". Un candidat suivi de 2x convient à un affichage avec un DPR de 2.

<img src="low-density.jpg" srcset="double-density.jpg 2x" alt="...">

Les navigateurs qui acceptent srcset se voient présenter deux candidatures: double-density.jpg, que 2x décrit comme approprié pour les écrans avec un DPR de 2, et low-density.jpg dans l'attribut src (le candidat sélectionné si rien de plus approprié n'est trouvé dans srcset). Pour les navigateurs qui ne sont pas compatibles avec srcset, l'attribut et son contenu sont ignorés. Le contenu de src est demandé, comme d'habitude.

Il est facile de confondre les valeurs spécifiées dans l'attribut srcset avec des instructions. Ce 2x informe le navigateur que le fichier source associé peut être utilisé sur un écran avec un DPR de 2 (informations sur la source elle-même). Il n'indique pas au navigateur comment utiliser cette source, mais il l'informe simplement comment la source pourrait être utilisée. Il s'agit d'une distinction subtile, mais importante: il s'agit d'une image à double densité, et non d'une image à utiliser sur un écran double densité.

La différence entre une syntaxe indiquant "Cette source est adaptée aux écrans 2x" et celle indiquant "Utiliser cette source sur les écrans 2x" est légère sur l'impression, mais la densité d'affichage n'est que l'un des nombreux facteurs interdépendants que le navigateur utilise pour déterminer le candidat à afficher. Vous ne pouvez en connaître qu'une partie. Par exemple, individuellement, il est possible de déterminer qu'un utilisateur a activé une préférence de navigateur pour économiser la bande passante via la requête média prefers-reduced-data, et de l'utiliser pour toujours proposer aux utilisateurs des images basse densité, quelle que soit leur densité d'affichage. Toutefois, à moins d'une mise en œuvre cohérente par chaque développeur et sur chaque site Web, cela ne serait pas d'une grande utilité pour l'utilisateur. Ils peuvent avoir leur préférence sur un site et se heurter à un mur d'images qui efface la bande passante sur le suivant.

L'algorithme de sélection des ressources délibérément vague utilisé par srcset/sizes laisse aux navigateurs la possibilité de choisir des images de densité plus faible avec des baisses de bande passante, ou en fonction d'une préférence pour minimiser la consommation de données, sans que nous assumions la responsabilité de comment, quand ou à quel seuil. Il n'est pas logique d'assumer des responsabilités, et du travail supplémentaire, que le navigateur soit mieux à même de gérer pour vous.

Décrire les largeurs avec w

srcset accepte un deuxième type de descripteur pour les sources d'image candidates. Il s'agit d'un modèle beaucoup plus puissant, et pour nos besoins, beaucoup plus facile à comprendre. Plutôt que d'indiquer qu'un candidat possède les dimensions appropriées pour une densité d'affichage donnée, la syntaxe w décrit la largeur inhérente à chaque source candidate. Là encore, chaque candidat est identique, à l'exception de ses dimensions : le même contenu, le même recadrage et les mêmes proportions. Toutefois, dans ce cas, vous souhaitez que le navigateur de l'utilisateur ait le choix entre deux options : small.jpg, d'une largeur inhérente de 600 pixels, et large.jpg, une source d'une largeur inhérente de 1 200 pixels.

srcset="small.jpg 600w, large.jpg 1200w"

Le navigateur n'indique pas ce qu'il doit faire avec ces informations, mais une liste d'options d'affichage pour l'image. Pour que le navigateur puisse prendre une décision concernant la source à afficher, vous devez lui fournir quelques informations supplémentaires: une description du rendu de l'image sur la page. Pour ce faire, utilisez l'attribut sizes.

Décrire l'utilisation avec sizes

Les navigateurs sont incroyablement performants lorsqu'il s'agit de transférer des images. Les requêtes de composants Image sont lancées bien avant les requêtes de feuilles de style ou de JavaScript, souvent avant même que le balisage ait été entièrement analysé. Lorsque le navigateur effectue ces requêtes, il ne dispose d'aucune information sur la page elle-même, hormis le balisage. Il n'a peut-être même pas encore initié de requêtes de feuilles de style externes, et encore moins les a appliquées. Au moment où le navigateur analyse votre balisage et commence à envoyer des requêtes externes, il ne dispose que d'informations au niveau du navigateur: la taille de la fenêtre d'affichage de l'utilisateur, la densité de pixels de l'écran, les préférences utilisateur, etc.

Cela ne nous donne rien sur la façon dont une image est censée s'afficher dans la mise en page. Elle ne peut même pas utiliser la fenêtre d'affichage comme proxy de la limite supérieure de la taille de img, car elle peut occuper un conteneur à défilement horizontal. Nous devons donc fournir ces informations au navigateur et le faire à l'aide d'un balisage. C'est tout ce que nous pouvons utiliser pour ces demandes.

Comme srcset, sizes est destiné à rendre des informations sur une image disponibles dès que le balisage est analysé. Tout comme l'attribut srcset est la forme abrégée de "Voici les fichiers sources et leur taille inhérente", l'attribut sizes est la forme abrégée de "voici la taille de l'image affichée dans la mise en page". La façon dont vous décrivez l'image est relative à la fenêtre d'affichage. Ici encore, la taille de la fenêtre d'affichage est les seules informations de mise en page dont dispose le navigateur au moment de la demande d'image.

Cela peut sembler un peu compliqué sur papier, mais dans la pratique, c'est beaucoup plus facile à comprendre:

<img
 sizes="80vw"
 srcset="small.jpg 600w, medium.jpg 1200w, large.jpg 2000w"
 src="fallback.jpg"
 alt="...">

Ici, cette valeur sizes indique au navigateur que l'espace occupé par img dans notre mise en page a une largeur de 80vw, soit 80% de la fenêtre d'affichage. N'oubliez pas qu'il ne s'agit pas d'une instruction, mais d'une description de la taille de l'image dans la mise en page. Elle n'indique pas "faire en sorte que cette image occupe 80% de la fenêtre d'affichage", mais "cette image finira par occuper 80% de la fenêtre d'affichage une fois la page affichée".

En tant que développeur, votre travail est terminé. Vous avez décrit avec précision une liste de sources candidates dans srcset et la largeur de votre image dans sizes. Comme pour la syntaxe x dans srcset, le reste dépend du navigateur.

Afin de bien comprendre comment ces informations sont utilisées, prenons le temps de passer en revue les décisions que le navigateur d'un utilisateur prend lorsqu'il rencontre ce balisage:

Vous avez indiqué au navigateur que cette image occupera 80% de la fenêtre d'affichage disponible. Par conséquent, si nous devons afficher cette img sur un appareil avec une fenêtre d'affichage de 1 000 pixels de large, cette image occupera 800 pixels. Le navigateur prend ensuite cette valeur et la divise par rapport aux largeurs de chacun des candidats de source d'image que nous avons spécifiés dans srcset. La plus petite source a une taille inhérente de 600 pixels, donc: 600÷800=0,75. Notre image moyenne mesure 1 200 pixels de largeur: 1 200 ÷ 800=1,5. L'image la plus grande fait 2 000 pixels de largeur: 2 000 ÷ 800=2,5.

Les résultats de ces calculs (.75, 1.5 et 2.5) sont, en fait, des options de DPR spécifiquement adaptées à la taille de la fenêtre d'affichage de l'utilisateur. Étant donné que le navigateur dispose également d'informations sur la densité d'affichage de l'utilisateur, il prend plusieurs décisions:

Pour cette taille de fenêtre d'affichage, la suggestion small.jpg est supprimée quelle que soit la densité d'affichage de l'utilisateur. Avec un DPR calculé inférieur à 1, cette source nécessiterait une augmentation de la taille pour n'importe quel utilisateur. Elle n'est donc pas appropriée. Sur un appareil avec un DPR de 1, medium.jpg fournit la correspondance la plus proche. Cette source est adaptée à un affichage avec un DPR de 1.5. Elle est donc un peu plus grande que nécessaire, mais n'oubliez pas que la réduction de la taille est un processus visuellement fluide. Sur un appareil avec un DPR de 2,large.jpg est la correspondance la plus proche, il est donc sélectionné.

Si la même image est affichée dans une fenêtre d'affichage de 600 pixels de large, le résultat de ces calculs sera complètement différent: 80 vw est désormais de 480 pixels. Lorsque nous divisons les largeurs de nos sources par rapport à cela, nous obtenons 1.25, 2.5 et 4.1666666667. À cette taille de fenêtre d'affichage, small.jpg sera choisi sur les appareils x1 et medium.jpg sur les appareils x2.

Cette image sera identique dans tous ces contextes de navigation: tous nos fichiers sources sont exactement les mêmes en dehors de leurs dimensions, et chacun s'affiche avec une netteté inégalée en fonction de la densité d'affichage de l'utilisateur. Toutefois, au lieu de diffuser large.jpg à chaque utilisateur afin de s'adapter aux plus grandes fenêtres d'affichage et à la densité d'affichage la plus élevée, le candidat le plus petit lui sera toujours proposé. En utilisant une syntaxe descriptive plutôt qu'une syntaxe normative, vous n'avez pas besoin de définir manuellement des points d'arrêt ni de prendre en compte les futures fenêtres d'affichage et DPR. Il vous suffit de fournir des informations au navigateur et de lui permettre de déterminer les réponses à votre place.

Comme notre valeur sizes est relative à la fenêtre d'affichage et complètement indépendante de la mise en page, elle ajoute une couche de complication. Il est rare qu'une image n'occupe qu'un pourcentage de la fenêtre d'affichage, sans marges à largeur fixe, sans marge intérieure, ni influence des autres éléments de la page. Vous devrez fréquemment exprimer la largeur d'une image à l'aide d'une combinaison d'unités : pourcentages, em, px, etc.

Heureusement, vous pouvez utiliser calc() ici. Tout navigateur compatible de manière native avec les images responsives acceptera également calc(), ce qui nous permet de combiner les unités CSS (par exemple, une image qui occupe toute la largeur de la fenêtre d'affichage de l'utilisateur, moins une marge de 1em de chaque côté) :

<img
    sizes="calc(100vw-2em)"
    srcset="small.jpg 400w, medium.jpg 800w, large.jpg 1600w, x-large.jpg 2400w"
    src="fallback.jpg"
    alt="...">

Décrire les points d'arrêt

Si vous avez passé beaucoup de temps à travailler avec des mises en page responsives, vous avez probablement remarqué qu'il manque quelque chose dans ces exemples : l'espace occupé par une image dans une mise en page est très susceptible de changer entre les points d'arrêt de la mise en page. Dans ce cas, vous devez transmettre un peu plus de détails au navigateur: sizes accepte un ensemble de candidats séparés par une virgule pour la taille de rendu de l'image, tout comme srcset accepte des candidats séparés par une virgule pour les sources d'image. Ces conditions utilisent la syntaxe de requête média familière. Cette syntaxe est basée sur la première correspondance: dès qu'une condition de média correspond, le navigateur cesse d'analyser l'attribut sizes, et la valeur spécifiée est appliquée.

Supposons qu'une image soit censée occuper 80% de la fenêtre d'affichage, moins un em de marge intérieure de chaque côté, dans les fenêtres d'affichage supérieures à 1 200 pixels. Dans les fenêtres d'affichage plus petites, elle occupe toute la largeur de la fenêtre d'affichage.

  <img
     sizes="(min-width: 1200px) calc(80vw - 2em), 100vw"
     srcset="small.jpg 600w, medium.jpg 1200w, large.jpg 2000w"
     src="fallback.jpg"
     alt="...">

Si la fenêtre d'affichage de l'utilisateur est supérieure à 1 200 pixels, calc(80vw - 2em) décrit la largeur de l'image de notre mise en page. Si la condition (min-width: 1200px) ne correspond pas, le navigateur passe à la valeur suivante. Comme aucune condition multimédia spécifique n'est liée à cette valeur, 100vw est utilisé par défaut. Si vous écrivez cet attribut sizes à l'aide de requêtes média max-width:

  <img
     sizes="(max-width: 1200px) 100vw, calc(80vw - 2em)"
     srcset="small.jpg 600w, medium.jpg 1200w, large.jpg 2000w"
     src="fallback.jpg"
     alt="...">

En langage clair: "Est-ce que (max-width: 1200px) correspond ? Sinon, passez à autre chose. La valeur suivante (calc(80vw - 2em)) ne comporte aucune condition éligible. Il s'agit donc de celle qui est sélectionnée.

Maintenant que vous avez fourni au navigateur toutes les informations sur votre élément img (sources potentielles, largeurs inhérentes et manière dont vous comptez présenter l'image à l'utilisateur), le navigateur utilise un ensemble confus de règles pour déterminer l'action à effectuer avec ces informations. Si cela semble vague, eh bien, c'est parce que c'est le cas – par conception. L'algorithme de sélection des sources encodé dans la spécification HTML est explicitement vague quant à la manière de choisir une source. Une fois que les sources, leurs descripteurs et le mode de rendu de l'image ont été analysés, le navigateur est libre de faire ce qu'il veut. Vous ne pouvez pas savoir avec certitude quelle source le navigateur choisira.

Une syntaxe qui indique "utiliser cette source sur un écran haute résolution" serait prévisible, mais elle ne résoudrait pas le problème principal des images dans une mise en page responsive: préserver la bande passante de l'utilisateur. La densité de pixels d'un écran n'est que proportionnellement liée à la vitesse de la connexion Internet, le cas échéant. Si vous utilisez un ordinateur portable haut de gamme, mais que vous naviguez sur le Web via une connexion limitée, le partage de connexion avec votre téléphone ou une connexion Wi-Fi d'avion qui tremble, vous pouvez désactiver les sources d'images haute résolution, quelle que soit la qualité de l'écran.

Laisser le dernier mot au navigateur permet d'améliorer les performances bien plus qu'avec une syntaxe strictement normative. Par exemple, dans la plupart des navigateurs, un img utilisant la syntaxe srcset ou sizes ne demandera jamais une source de dimensions inférieures à celles que l'utilisateur possède déjà dans le cache de son navigateur. À quoi sert-il d'envoyer une nouvelle requête pour une source qui semble identique, alors que le navigateur peut facilement réduire la source d'image dont il dispose déjà ? Toutefois, si l'utilisateur adapte la fenêtre d'affichage au point où une nouvelle image est nécessaire afin d'éviter l'augmentation de la taille, cette requête sera quand même effectuée pour que tout soit conforme à vos attentes.

Ce manque de contrôle explicite peut sembler un peu effrayant à première vue, mais comme vous utilisez des fichiers sources avec un contenu identique, il est peu probable que l'expérience des utilisateurs soit "rompue " qu'avec une src à source unique, quelles que soient les décisions prises par le navigateur.

Utiliser sizes et srcset

Cela représente une grande quantité d'informations, à la fois pour vous, le lecteur et le navigateur. srcset et sizes sont des syntaxes denses, qui décrivent une quantité choquante d'informations en relativement peu de caractères. En d'autres termes, le fait que ces syntaxes soient moins succinctes et plus faciles à analyser par les humains aurait pu les rendre plus difficiles à analyser pour un navigateur. Plus une chaîne est complexe, plus il y a de risques d'erreurs d'analyse ou de différences de comportement involontaires d'un navigateur à un autre. Il y a toutefois un avantage ici: une syntaxe plus lisible par les machines est plus facile à écrire par elles.

srcset est un cas clair pour l'automatisation. Il est rare de créer manuellement plusieurs versions de vos images pour un environnement de production, au lieu d'automatiser le processus à l'aide d'un exécuteur de tâches comme Gulp, d'un bundler comme Webpack, d'un CDN tiers tel que Cloudinary ou d'une fonctionnalité déjà intégrée au CMS de votre choix. Si nous disposions de suffisamment d'informations pour générer nos sources en premier lieu, un système disposerait de suffisamment d'informations pour les écrire dans un attribut srcset viable.

sizes est un peu plus difficile à automatiser. Comme vous le savez, le seul moyen pour un système de calculer la taille d'une image dans une mise en page affichée est de l'afficher. Heureusement, de nombreux outils pour les développeurs sont apparus pour éliminer le processus d'écriture manuscrite des attributs sizes, avec une efficacité que vous ne pouviez jamais comparer à la main. respImageLint, par exemple, est un extrait de code destiné à vérifier la précision de vos attributs sizes et à fournir des suggestions d'amélioration. Le projet Lazysizes compromet la rapidité de l'efficacité en différant les requêtes d'image jusqu'à ce que la mise en page soit établie, ce qui permet à JavaScript de générer des valeurs sizes à votre place. Si vous utilisez un framework de rendu entièrement côté client tel que React ou Vue, il existe plusieurs solutions pour créer et/ou générer les attributs srcset et sizes. Nous les aborderons plus en détail dans la section CMS et frameworks.