Automatiser la sélection des ressources avec des indications client

Ilya Grigorik
Ilya Grigorik

Créer des applications pour le Web vous offre une portée inégalée. En un clic, votre application Web est disponible sur presque tous les appareils connectés (smartphones, tablettes, ordinateurs portables et de bureau, téléviseurs et plus encore), indépendamment de la marque ou de la plate-forme. Pour offrir la meilleure expérience possible, vous avez créé un site responsif dont la présentation et les fonctionnalités s'adaptent à chaque facteur de forme. Vous exécutez maintenant la checklist des performances pour vous assurer que l'application se charge le plus rapidement possible: vous avez optimisé le chemin critique du rendu, vous avez compressé et mis en cache vos ressources textuelles, et vous examinez maintenant vos ressources d'image, qui représentent souvent la majorité des octets. Le problème est que l'optimisation des images est difficile:

  • Déterminer le format approprié (vecteur ou matriciel)
  • Déterminer les formats d'encodage optimaux (jpeg, webp, etc.)
  • Déterminer les bons paramètres de compression (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, de la vitesse et des préférences de l'utilisateur.

Individuellement, ces problèmes sont bien compris. Ensemble, elles constituent un vaste espace d'optimisation que nous (les développeurs) négligons ou négligons souvent. L'humain explore le même espace de recherche de manière répétitive, en particulier lorsque de nombreuses étapes sont nécessaires. Les ordinateurs, en revanche, excellent dans ce type de tâches.

La réponse à une stratégie d'optimisation efficace et durable pour les images et les autres ressources ayant des propriétés similaires est simple: l'automatisation. Si vous ajustez manuellement vos ressources, vous vous trompez: vous oublierez, vous deviendrez paresseux ou quelqu'un d'autre fera cette erreur à votre place, c'est garanti.

La saga du développeur axé sur les performances

La recherche dans l'espace d'optimisation d'images comporte deux phases distinctes: la compilation et 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 à l'origine de la demande et doivent être effectuées au "moment de l'exécution": sélectionner la ressource appropriée pour le DPR du client et la largeur d'affichage prévue, tenir 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 ils pourraient être améliorés. Par exemple, il est possible de réaliser de nombreuses économies en réglant dynamiquement le paramètre de "qualité" pour chaque image et chaque format d'image, mais je n'ai encore vu personne ne l'utiliser en dehors de la recherche. C'est un domaine propice à l'innovation, mais pour les besoins de cet article, je vais en rester là. Concentrons-nous sur la partie de l'histoire.

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

L'intention de l'application est très simple: récupérer et afficher l'image à 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 axé sur les performances de l'équipe passe une longue nuit:

  1. Pour obtenir une compression optimale, elle souhaite utiliser le format d'image optimal pour chaque client: WebP pour Chrome, JPEG XR pour Edge et JPEG pour le reste.
  2. Pour obtenir la meilleure qualité visuelle, elle doit générer plusieurs variantes de chaque image dans différentes résolutions: 1x, 1,5x, 2x, 2,5x, 3x et peut-être même quelques autres entre les deux.
  3. Pour éviter d'afficher des pixels inutiles, elle doit comprendre ce que "50% de la fenêtre d'affichage de l'utilisateur" signifie réellement, car il existe de nombreuses largeurs de fenêtre d'affichage différentes.
  4. Idéalement, elle souhaite également offrir une expérience résiliente dans laquelle les utilisateurs sur des réseaux plus lents récupéreront automatiquement une résolution inférieure. Après tout, il est temps de passer au verre.
  5. L'application expose également certaines commandes utilisateur qui affectent les ressources d'image à récupérer. Vous devez donc en tenir compte.

Le concepteur se rend ensuite compte qu'il doit afficher une image différente à 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 élément, puis appliquer l'extraction en fonction de la taille de la fenêtre d'affichage. Ai-je mentionné que ces choses sont difficiles ? Eh bien, OK, allons-y. L'élément picture nous mène assez 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. Impressionnant !

Malheureusement, l'élément picture ne nous permet pas de définir des 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 qu'il extrait dans certains cas (voir l'étape 5). Il suffit d'espérer que le user-agent est assez intelligent. (Remarque: aucune des implémentations actuelles ne l'est.) De même, il n'existe pas de hooks dans l'élément picture pour permettre une logique spécifique à l'application qui tient compte des préférences de l'application ou de l'utilisateur. Pour obtenir ces deux derniers bits, il faudrait déplacer toute la logique ci-dessus dans JavaScript, mais les optimisations d'analyseur de préchargement proposées par picture seront perdues. Hmm.

Ces limites mises à part, cela fonctionne. Eh bien, au moins pour cet élément en particulier. Le vrai défi, et celui à long terme, est que le concepteur ou le développeur ne peuvent pas élaborer manuellement un code comme celui-ci pour chaque élément. C'est un casse-tête amusant au premier coup, mais il perd immédiatement son attrait. Nous avons besoin d'automatisation. L'IDE ou un autre outil de transformation de contenu peut peut-être nous sauver et générer automatiquement le code récurrent ci-dessus.

Automatiser la sélection des ressources avec les indications du 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 suffit pour offrir les mêmes fonctionnalités que le balisage d'image beaucoup plus long ci-dessus et, comme nous le verrons, il permet aux développeurs de contrôler entièrement comment, quelles ressources d'image et quand elles sont extraites. La "magique" se trouve dans la première ligne qui active la création de rapports de hints client et indique au navigateur d'annoncer le ratio de pixels de l'appareil (DPR), la largeur de la fenêtre d'affichage (Viewport-Width) et la largeur d'affichage prévue (Width) des ressources sur le serveur.

Lorsque les suggestions client sont activées, 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 distribués, ni des autres critères de sélection des ressources. Soyons réalistes, ils ne l'ont jamais fait, et ils ne devraient pas avoir à le faire. Mieux encore, le développeur n'a pas 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 offre une compatibilité native avec les indications DPR, Width et Viewport-Width. Les optimisations sont désactivées par défaut, et le <meta http-equiv="Accept-CH" content="..."> ci-dessus sert de signal d'activation qui indique à Chrome d'ajouter les en-têtes spécifiés aux requêtes sortantes. Une fois que cela est en place, examinons les en-têtes de requête et de réponse pour obtenir un exemple de requête d'image:

Diagramme de négociation des suggestions du 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 compatibilité avec JPEG XR via l'en-tête Accept.

Les trois en-têtes de requête suivants sont les en-têtes client-hint qui annoncent le ratio de pixels de l'appareil du client (3x), la largeur de la fenêtre d'affichage de la mise en page (460 px) et la largeur d'affichage prévue de la ressource (230 px). Cela fournit toutes les informations nécessaires au serveur 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 suggestions 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 dans ce cas. Nous avons déplacé la sélection des ressources du balisage HTML vers la négociation demande-réponse entre le client et le serveur. Par conséquent, le code HTML ne concerne que les exigences de présentation et nous pouvons faire confiance à n'importe quel concepteur et développeur pour l'écrire, tandis que la recherche dans l'espace d'optimisation des images est différée pour les ordinateurs et peut désormais être facilement automatisée à grande échelle. Vous vous souvenez de notre développeur axé sur les performances ? Son travail consiste maintenant à écrire un service d'images capable d'exploiter les indications 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 s'en charger à sa place.

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

Tu te souviens de celui-là ? Avec les indications du client, le tag d'image simple tient désormais compte de DPR, de la fenêtre d'affichage et de la largeur, sans aucun balisage supplémentaire. Si vous avez besoin d'ajouter une direction artistique, vous pouvez utiliser le tag picture, comme indiqué ci-dessus. Sinon, tous vos tags d'image existants sont désormais beaucoup 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 effet un proxy côté client exécuté dans votre navigateur. Il 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 hints client sont activés, le serviceWorker actif peut identifier les requêtes d'image, inspecter les suggestions du client fournies 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])
    }
    ...
}
Hints client serviceWorker.

ServiceWorker vous permet de contrôler entièrement la sélection des ressources côté client. Cette étape est cruciale. Enfoncez-vous, car les possibilités sont presque infinies:

  • Vous pouvez réécrire les valeurs d'en-tête des suggestions de clients définies par le user-agent.
  • Vous pouvez ajouter à la requête de nouvelles valeurs d'en-têtes d'indicateurs client.
  • Vous pouvez réécrire l'URL et diriger la requête d'image vers un serveur alternatif (par exemple, un CDN).
    • Vous pouvez même déplacer les valeurs des indications des 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é de l'utilisateur.
  • Vous pouvez prendre en compte les remplacements de préférences d'application et d'utilisateur.
  • Tu peux vraiment... faire tout ce que ton cœur souhaite, vraiment.

L'élément picture fournit le contrôle de direction artistique nécessaire dans le balisage HTML. Les hints client fournissent des annotations sur les requêtes d'image résultantes, ce qui permet d'automatiser la sélection des ressources. ServiceWorkers fournit des fonctionnalités de gestion des requêtes et des réponses sur le client. Voilà comment fonctionne le Web extensible.

Questions fréquentes sur les hints client

  1. Où les conseils destinés aux clients sont-ils disponibles ? Expédié dans Chrome 46. En cours d'examen dans Firefox et Edge.

  2. Pourquoi les indications client sont-elles activées ? Nous souhaitons réduire les frais généraux pour les sites qui n'utilisent pas les indications client. Pour activer les indications client, le site doit fournir l'en-tête Accept-CH ou une instruction <meta http-equiv> équivalente dans le balisage de la page. Si 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 proposerons peut-être un mécanisme supplémentaire afin 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 des indications client si nous disposons de 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. En tout cas, pas sans introduire des allers-retours coûteux et retarder considérablement la demande d'image, par exemple lorsqu'une demande 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 indications client ne concernent-elles que les ressources d'image ? Le principal cas d'utilisation derrière les suggestions de DPR, de largeur de fenêtre d'affichage et de largeur consiste à activer la sélection des ressources pour les composants Image. Cependant, les mêmes indications sont fournies pour toutes les sous-ressources, quel que soit leur type. Par exemple, les requêtes CSS et JavaScript obtiennent également les mêmes informations et peuvent également être utilisées pour optimiser ces ressources.

  5. La largeur n'est pas indiquée pour certaines demandes d'images. Pourquoi ? 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 ces 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, veillez à spécifier une valeur de taille pour vos images.

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

  7. Où puis-je en savoir plus, regarder d'autres démos, et que faire ? Consultez l'explication et n'hésitez pas à signaler un problème sur GitHub si vous avez des commentaires ou d'autres questions.