Automatiser la sélection des ressources avec des indications client

Ilya Grigorik

Développer pour le Web vous permet de toucher une audience sans pareil. Votre application Web est accessible en un clic et disponible sur presque tous les appareils connectés (smartphones, tablettes, ordinateurs portables et de bureau, téléviseurs, etc.), indépendamment de la marque ou de la plate-forme. Pour une expérience optimale, vous avez créé un site responsif qui adapte la présentation et les fonctionnalités à chaque facteur de forme. Vous allez maintenant analyser votre checklist de performances pour vous assurer que l'application se charge aussi rapidement que possible: vous avez optimisé le chemin d'affichage critique, vous avez compressé et mis en cache vos ressources textuelles et vous examinez maintenant la majorité des octets transférés. Le problème est le suivant : l'optimisation des images est difficile :

  • Déterminer le format approprié (vecteur ou trame)
  • Déterminer les formats d'encodage optimaux (jpeg, webp, etc.)
  • Déterminer les paramètres de compression appropriés (avec ou sans perte)
  • Déterminer les métadonnées à conserver ou à supprimer
  • Créez plusieurs variantes de chacune pour chaque écran et résolution DPR.
  • ...
  • Tenez compte du type de réseau, du débit et des préférences de l'utilisateur.

Individuellement, il s'agit de problèmes bien compris. Ils créent collectivement un vaste espace d'optimisation que nous (les développeurs) négligons ou négligons. L'exploration humaine d'un même espace de recherche de manière répétitive, en particulier lorsqu'il y a de nombreuses étapes, n'est pas optimale. Les ordinateurs, en revanche, excellent dans ce genre de tâches.

Pour élaborer une stratégie d'optimisation efficace et durable pour les images et les autres ressources présentant des propriétés similaires, la solution est simple: l'automatisation. Si vous ajustez manuellement vos ressources, vous vous trompez: vous l'oublierez, vous deviendrez paresseux ou quelqu'un d'autre fera ces erreurs à votre place, ce qui est garanti.

La saga du développeur soucieux des performances

La recherche dans l'espace d'optimisation d'images comporte deux phases distinctes: la durée de la compilation et la durée de l'exécution.

  • Certaines optimisations sont inhérentes à la ressource elle-même : sélection du format et du type d'encodage appropriés, réglage des paramètres de compression pour chaque encodeur, suppression des métadonnées inutiles, etc. Ces étapes peuvent être effectuées "au moment de la compilation".
  • D'autres optimisations sont déterminées par le type et les propriétés du client qui les demande et doivent être effectuées au moment de l'exécution : sélection de la ressource adaptée au DPR du client et à la largeur d'affichage prévue, en tenant compte de la vitesse du réseau du client, des préférences de l'utilisateur et de l'application, etc.

Les outils de compilation existent, mais pourraient être améliorés. Par exemple, le réglage dynamique du paramètre de qualité pour chaque image et chaque format d'image permet de réaliser de nombreuses économies, mais personne ne l'utilise réellement en dehors des recherches. Il s'agit d'un domaine propice à l'innovation, mais pour les besoins de cet article, je vais en rester là. Concentrons-nous sur la durée de l'histoire.

<img src="/image/thing" sizes="50vw"
        alt="image thing displayed at 50% of viewport width">

L'intent de l'application est très simple: récupérer et afficher l'image dans 50% de la fenêtre d'affichage de l'utilisateur. C'est là que la plupart des concepteurs se lavent les mains et la tête pour le bar. Pendant ce temps, le développeur de l'équipe axé sur les performances va passer une longue nuit:

  1. Pour obtenir la meilleure compression possible, elle souhaite utiliser le format d'image optimal pour chaque client: WebP pour Chrome, JPEG XR pour Edge et JPEG, entre autres.
  2. Pour obtenir la meilleure qualité visuelle, elle doit générer plusieurs variantes de chaque image à différentes résolutions: 1x, 1,5x, 2x, 2,5x, 3x, voire quelques autres.
  3. Pour éviter de diffuser des pixels inutiles, elle doit comprendre ce que signifie réellement "50% de la fenêtre d'affichage de l'utilisateur". Il existe de nombreuses largeurs de fenêtre d'affichage.
  4. Idéalement, elle souhaite également offrir une expérience résiliente, où les utilisateurs de réseaux plus lents récupéreront automatiquement une résolution inférieure. Après tout, il est impératif de consommer un verre.
  5. L'application expose également certaines commandes utilisateur qui déterminent la ressource image à extraire. Vous devez donc également en tenir compte.

Le graphiste se rend compte alors qu'il doit afficher une autre image à 100% de la largeur si la taille de la fenêtre d'affichage est petite afin d'optimiser la lisibilité. Cela signifie que nous devons maintenant répéter le même processus pour un autre asset, puis rendre la récupération conditionnelle en fonction de la taille de la fenêtre d'affichage. Ai-je mentionné que c'était compliqué ? Eh bien, OK, commençons. L'élément picture nous mènera loin:

<picture>
    <!-- serve WebP to Chrome and Opera -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.webp 200w, /image/thing-400.webp 400w,
        /image/thing-800.webp 800w, /image/thing-1200.webp 1200w,
        /image/thing-1600.webp 1600w, /image/thing-2000.webp 2000w"
    type="image/webp">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.webp 200w, /image/thing-crop-400.webp 400w,
        /image/thing-crop-800.webp 800w, /image/thing-crop-1200.webp 1200w,
        /image/thing-crop-1600.webp 1600w, /image/thing-crop-2000.webp 2000w"
    type="image/webp">
    <!-- serve JPEGXR to Edge -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.jpgxr 200w, /image/thing-400.jpgxr 400w,
        /image/thing-800.jpgxr 800w, /image/thing-1200.jpgxr 1200w,
        /image/thing-1600.jpgxr 1600w, /image/thing-2000.jpgxr 2000w"
    type="image/vnd.ms-photo">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.jpgxr 200w, /image/thing-crop-400.jpgxr 400w,
        /image/thing-crop-800.jpgxr 800w, /image/thing-crop-1200.jpgxr 1200w,
        /image/thing-crop-1600.jpgxr 1600w, /image/thing-crop-2000.jpgxr 2000w"
    type="image/vnd.ms-photo">
    <!-- serve JPEG to others -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.jpg 200w, /image/thing-400.jpg 400w,
        /image/thing-800.jpg 800w, /image/thing-1200.jpg 1200w,
        /image/thing-1600.jpg 1600w, /image/thing-2000.jpg 2000w">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.jpg 200w, /image/thing-crop-400.jpg 400w,
        /image/thing-crop-800.jpg 800w, /image/thing-crop-1200.jpg 1200w,
        /image/thing-crop-1600.jpg 1600w, /image/thing-crop-2000.jpg 2000w">
    <!-- fallback for browsers that don't support picture -->
    <img src="/image/thing.jpg" width="50%">
</picture>

Nous avons géré la direction artistique et la sélection du format, et fourni six variantes de chaque image pour tenir compte de la variabilité de la DPR et de la largeur de la fenêtre d'affichage de l'appareil du client. Incroyable !

Malheureusement, l'élément picture ne nous permet pas de définir de règles de comportement en fonction du type de connexion ou de la vitesse du client. Cela dit, son algorithme de traitement permet au user-agent d'ajuster les ressources récupérées dans certains cas (voir l'étape 5). Il nous suffit d'espérer que l'user-agent est assez intelligent. (Remarque: aucune des implémentations actuelles ne le fait.) De même, il n'y a pas de hooks dans l'élément picture pour permettre une logique spécifique à l'application qui tient compte des préférences des applications ou des utilisateurs. Pour obtenir ces deux derniers bits, nous devrions déplacer toute la logique ci-dessus dans JavaScript, mais cela renonce aux optimisations de l'analyse de préchargement proposées par picture. Hmm.

Mis à part ces limites, elle fonctionne. Eh bien, du moins pour cet asset particulier. Le véritable défi, à long terme, est que nous ne pouvons pas espérer que le graphiste ou le développeur crée manuellement un tel code pour chaque élément. C'est un casse-tête amusant au premier essai, mais il perd immédiatement son attrait. Nous avons besoin d'automatisation. Peut-être que l'IDE ou d'autres outils de transformation de contenu peuvent nous sauver et générer automatiquement le code récurrent ci-dessus.

Automatiser la sélection des ressources avec des indications client

Respirez profondément, suspendez votre incrédulité et considérez maintenant l'exemple suivant:

<meta http-equiv="Accept-CH" content="DPR, Viewport-Width, Width">
...
<picture>
    <source media="(min-width: 50em)" sizes="50vw" srcset="/image/thing">
    <img sizes="100vw" src="/image/thing-crop">
</picture>

Croyez-le ou non, l'exemple ci-dessus est suffisant pour fournir les mêmes fonctionnalités que le balisage d'images, qui est beaucoup plus long, ci-dessus. Comme nous le verrons, il permet également aux développeurs de contrôler entièrement l'extraction des ressources image, de quelle manière et à quel moment. La "magie" figure dans la première ligne qui active la création de rapports sur les indications client et indique au navigateur d'annoncer le ratio de pixels de l'appareil (DPR), la largeur de la fenêtre d'affichage de mise en page (Viewport-Width) et la largeur d'affichage prévue (Width) des ressources sur le serveur.

Lorsque les indicateurs client sont activés, le balisage côté client obtenu ne conserve que les exigences de présentation. Le concepteur n'a pas à se soucier des types d'images, des résolutions des clients, des points d'arrêt optimaux pour réduire les octets diffusés, ni d'autres critères de sélection des ressources. Soyons réalistes, ils ne l'ont jamais fait, et ils n'auraient pas dû à le faire. Mieux encore, le développeur n'a pas non plus besoin de réécrire ni de développer le balisage ci-dessus, car la sélection réelle des ressources est négociée par le client et le serveur.

Chrome 46 est compatible en natif avec les indications DPR, Width et Viewport-Width. Les optimisations sont désactivées par défaut et la valeur <meta http-equiv="Accept-CH" content="..."> ci-dessus sert de signal d'activation qui demande à Chrome d'ajouter les en-têtes spécifiés aux requêtes sortantes. Examinons maintenant les en-têtes de requête et de réponse d'un exemple de requête d'image:

Schéma de négociation des signaux client

Chrome annonce sa compatibilité avec le format WebP via l'en-tête de requête Accept. Le nouveau navigateur Edge annonce également la prise en charge du format JPEG XR via l'en-tête Accept.

Les trois en-têtes de requête suivants sont les en-têtes d'indications client qui annoncent le ratio de pixels de l'appareil du client (3x), la largeur de la fenêtre d'affichage de mise en page (460 px) et la largeur d'affichage prévue de la ressource (230 px). Cela fournit au serveur toutes les informations nécessaires pour sélectionner la variante d'image optimale en fonction de son propre ensemble de règles: disponibilité des ressources prégénérées, coût du réencodage ou du redimensionnement d'une ressource, popularité d'une ressource, charge actuelle du serveur, etc. Dans ce cas particulier, le serveur utilise les indices DPR et Width et renvoie une ressource WebP, comme indiqué par les en-têtes Content-Type, Content-DPR et Vary.

Il n'y a pas de magie. Nous avons déplacé la sélection des ressources du balisage HTML vers la négociation requête-réponse entre le client et le serveur. Par conséquent, le code HTML ne concerne que les exigences de présentation. Nous pouvons faire confiance à tous les concepteurs et développeurs pour écrire, tandis que la recherche dans l'espace d'optimisation des images est différée pour les ordinateurs et est désormais facilement automatisée à grande échelle. Vous vous souvenez de notre développeur soucieux des performances ? Son travail consiste maintenant à créer un service d'images capable d'exploiter les suggestions fournies et de renvoyer la réponse appropriée. Elle peut utiliser le langage ou le serveur de son choix, ou laisser un service tiers ou un CDN le faire en son nom.

<img src="/image/thing" sizes="50vw"
        alt="image thing displayed at 50% of viewport width">

Tu te souviens de ce gars ci-dessus ? Grâce aux indications du client, la balise d'image simple tient désormais compte de la DPR, de la fenêtre d'affichage et de la largeur, sans balisage supplémentaire. Si vous devez ajouter une direction artistique, vous pouvez utiliser la balise picture, comme illustré ci-dessus. Sinon, tous vos tags d'image existants sont encore plus intelligents. Les indicateurs client améliorent les éléments img et picture existants.

Prendre le contrôle de la sélection des ressources avec un service worker

ServiceWorker est en fait un proxy côté client qui s'exécute dans votre navigateur. Elle intercepte toutes les requêtes sortantes et vous permet d'inspecter, de réécrire, de mettre en cache et même de synthétiser les réponses. Il en va de même pour les images. Lorsque les indicateurs client sont activés, le service Worker actif peut identifier les requêtes d'image, inspecter les indices client fournis et définir sa propre logique de traitement.

self.onfetch = function(event) {
    var req = event.request.clone();
    console.log("SW received request for: " + req.url)
    for (var entry of req.headers.entries()) {
    console.log("\t" + entry[0] +": " + entry[1])
    }
    ...
}
Indicateurs client serviceWorker.

ServiceWorker vous permet de contrôler entièrement la sélection des ressources côté client. C'est essentiel. Les possibilités sont presque infinies:

  • Vous pouvez réécrire les valeurs d'en-tête des indications client définies par le user-agent.
  • Vous pouvez ajouter de nouvelles valeurs d'en-têtes de indications client à la requête.
  • Vous pouvez réécrire l'URL et faire pointer la requête d'image vers un autre serveur (par exemple, CDN).
    • Vous pouvez même déplacer les valeurs des indications depuis les en-têtes vers l'URL elle-même si cela facilite le déploiement dans votre infrastructure.
  • Vous pouvez mettre en cache les réponses et définir votre propre logique pour la diffusion des ressources.
  • Vous pouvez adapter votre réponse en fonction de la connectivité des utilisateurs.
  • Vous pouvez prendre en compte les remplacements de préférences utilisateur et d'applications.
  • Tu peux... vraiment faire ce que tu veux.

L'élément picture fournit la commande de direction artistique nécessaire dans le balisage HTML. Les indications client fournissent des annotations sur les requêtes d'image résultantes qui permettent d'automatiser la sélection des ressources. ServiceWorker fournit des fonctionnalités de gestion des requêtes et des réponses au client. L'extensibilité du Web en action.

Questions fréquentes sur les indications client

  1. Où les indicateurs client sont-ils disponibles ? Expédié dans Chrome 46. En cours d'examen dans Firefox et Edge

  2. Pourquoi les conseils aux clients sont-ils activés ? Nous voulons réduire les frais généraux pour les sites qui n'utilisent pas les indicateurs client. Pour activer les indicateurs client, le site doit fournir l'en-tête Accept-CH ou une instruction <meta http-equiv> équivalente dans le balisage de la page. Une fois que l'un de ces éléments est présent, le user-agent ajoute les indications appropriées à toutes les requêtes de sous-ressources. À l'avenir, nous pourrons proposer un mécanisme supplémentaire permettant de conserver cette préférence pour une origine particulière, ce qui permettra d'envoyer les mêmes indications pour les requêtes de navigation.

  3. Pourquoi avons-nous besoin d'indicateurs client si nous avons ServiceWorker ? ServiceWorker n'a pas accès aux informations sur la mise en page, les ressources et la largeur de la fenêtre d'affichage. Du moins, ne pas sans introduire d'allers-retours coûteux et retarder considérablement la requête d'image, par exemple lorsqu'une requête d'image est lancée par l'analyseur de préchargement. Les indicateurs client s'intègrent au navigateur pour rendre ces données disponibles dans le cadre de la requête.

  4. Les optimisations client sont-elles réservées aux ressources image ? Le principal cas d'utilisation derrière les indications DPR, Viewport-Width et Largeur consiste à permettre la sélection des ressources pour les composants Image. Toutefois, les mêmes suggestions sont fournies pour toutes les sous-ressources, quel que soit leur type. Par exemple, les requêtes CSS et JavaScript reçoivent également les mêmes informations et peuvent également être utilisées pour optimiser ces ressources.

  5. Pourquoi certaines demandes d'images n'indiquent pas de largeur ? Il est possible que le navigateur ne connaisse pas la largeur d'affichage souhaitée, car le site s'appuie sur la taille intrinsèque de l'image. Par conséquent, la suggestion de largeur est omise pour ce type de requêtes, ainsi que pour celles qui n'ont pas de "largeur d'affichage" (par exemple, une ressource JavaScript). Pour recevoir des indications de largeur, assurez-vous de spécifier une valeur de taille sur vos images.

  6. Qu'en est-il de <insert my favorite hint> ? ServiceWorker permet aux développeurs d'intercepter et de modifier (par exemple, ajouter de nouveaux en-têtes) toutes les requêtes sortantes. Par exemple, vous pouvez facilement ajouter des informations basées sur NetInfo pour indiquer le type de connexion actuel. Consultez la section Créer des rapports de fonctionnalités avec ServiceWorker. Les indications "natives" fournies dans Chrome (DPR, largeur, largeur de ressource) sont implémentées dans le navigateur, car une implémentation basée uniquement sur les logiciels retarderait toutes les requêtes d'image.

  7. Où puis-je trouver de plus amples informations ou d'autres démonstrations, etc. Consultez le document explicatif et n'hésitez pas à signaler un problème sur GitHub si vous avez des commentaires ou d'autres questions.