Aller au contenu principal
divagations - Retour à l'accueil

Ajouter des images responsives avec une "moulinette magique"

Guillaume Barbier

Temps de lecture : ~ 7 minutes

En théorie (vous connaissez ce pays magique ?), il est assez facile avec Eleventy de générer des images responsives à la volée : un plugin "Image"(S'ouvre dans un nouvelle fenêtre) officiel propose une méthode "magique" appelée eleventyImageTransformPlugin.

Ibis rouge, croquis encre de chine sur carton-liège

Cette méthode repasse sur tous les fichiers HTML générés, repère les balises <img>, leur ajoute une balise <picture>, retrouve l'image source (quelle soit dans un dossier ou quelque part sur internet) et la page à la moulinette pour générer autant de renditions qu'on veut !

Truc de dingue !

Astuce

Renditions, kézako ?

Je vais pas mal parler de renditions dans cet entrée de journal, donc autant définir ce mot.

Renditions c'est un mot anglais qu'on utilise pour désigner des déclinaisons d'une image à différentes tailles et formats. Le visuel lui-même ne change pas, juste sa taille et son format… et en général son poids ! (C'est tout l'intérêt.)

Certains systèmes proposent aussi de recadrer les visuels à différents ratio, mais je n'ai pas testé plus que ça la bibliothèque utilisée par Eleventy (Sharp(Opens in a new window)) donc je ne sais pas si elle en était capable ou non.

Implémentation basique et naïve

C'est facile donc ne perdons pas de temps, j'implémente :

import { eleventyImageTransformPlugin } from "@11ty/eleventy-img";
import path from "node:path";

export default async function(eleventyConfig) {
  eleventyConfig.addPlugin(eleventyImageTransformPlugin, {
    formats: ["webp", "jpeg"],
    widths: ["320", "640", "720", "1440"],
    urlPath: "/_src/img/",
    filenameFormat: function (id, src, width, format, options) {
      const extension = path.extname(src);
      const name = path.basename(src, extension);
      return `${name}-${width}w.${format}`;
    },
    htmlOptions: {
      imgAttributes: {
        loading: "lazy"
      }
    },
    failOnError: false
  });
}

Quelques paramètres

Des le départ je pose quelques paramètres :

  • Je défini comme format le webp et le jpg, même si, en vrai, je n'ai pas besoin de le faire : c'est les valeurs par défaut (je l'ai fait juste pour ne pas oublier).
  • Définir les largeurs à générer : la largeur max des contenus dans mes articles (donc de mes images) est de 720px de large, je prévois donc les renditions suivantes :
    • 720px (la taille normale sur un ordinateur avec un écran normal)
    • 1440px (taille doublée pour les ordinateurs avec écrans retina)
    • 320px (taille pour le mobile - on reste sur un multiple de 16, comme d'habitude)
    • 640px (pour les petits mobiles avec écran retina)
  • Un format de nom de fichier car le nom généré automatiquement est horrible
  • J'ajoute un attribut loading="lazy" pour de meilleurs performances
  • L'option failOnError: false pour éviter qu'une erreur genre fichier manquant me plante tout le build Eleventy

Astuce

Renditions maximales

Un truc très cool avec le plugin d'Eleventy, et que je n'ai pas vu partout, c'est qu'il prend en compte la taille initiale de l'asset !

Je m'explique : Les générateurs de renditions classiques se contentent de fournir les tailles demandées, sans garde-fous :

  • Si je demande à un générateur classique de générer une rendition de 3000 pixels de large
  • Et que je lui fourni une image de 1024 pixels de large (donc plus petite)
  • Il va m'agrandir l'original afin de me générer une rendition toute pixelisée de 3000 pixels de large (et inutilement lourde en plus !)

Si je fais la même demande avec le générateur d'Eleventy, il ne tentera pas de générer une rendition à ce format démesuré et me proposera à la place une rendition à la même taille que l'original (vu que c'est la taille maximale disponible).

Avertissement

Il parait qu'il fallait pas toucher à l’emplacement des fichiers

La doc met en garde contre la modification de l'emplacement avec urlPath en mode "magique" (a priori c'est OK si on utilise d'autres modes plus avancés). Sauf qu'elle n'explique pas pourquoi, et moi je ne veux pas qu'il me les mette n'importe tous mes images… Donc tant pis ! On verra plus tard si ça casse un truc…

Le résultat

<picture>
  <source type="image/webp" sizes="auto"
    srcset="
      /_src/img/renditions/IbisRouge003-2-320w.webp 320w, 
      /_src/img/renditions/IbisRouge003-2-640w.webp 640w, 
      /_src/img/renditions/IbisRouge003-2-720w.webp 720w, 
      /_src/img/renditions/IbisRouge003-2-1024w.webp 1024w"
    >
  <img loading="lazy" 
    src="/_src/img/renditions/IbisRouge003-2-320w.jpeg" 
    width="1024" height="754" sizes="auto"
    alt="Ibis rouge, croquis encre de chine sur carton-liège" 
    srcset="
      /_src/img/renditions/IbisRouge003-2-320w.jpeg 320w, 
      /_src/img/renditions/IbisRouge003-2-640w.jpeg 640w, 
      /_src/img/renditions/IbisRouge003-2-720w.jpeg 720w, 
      /_src/img/renditions/IbisRouge003-2-1024w.jpeg 1024w"
    >
</picture>

Information

Code différent en contexte "serveur de test"

Lorsqu'on se contente de prévisualiser le site en local avec npx @11ty/eleventy --serve, les formats d'URL sont ignorés (probablement à cause de fonctionnalités d'optimisation de la génération – faites pour accélérer l'affichage en direct des modifications). Le code généré est similaire même si, du coup, les URLs sont un peu moins lisibles.

Exemple : /.11ty/image/?src=content%2F_src%2Fimg%2FIbisRouge003-2.jpg&width=320&format=jpeg&via=transform

C'est cool, c'est simple… mais en vrai je me farci plein de galères avec cette implémentation

Mes galères

Le plugin bulldozer !

Ce plugin repasse sur littéralement toutes les images du site même celles que je ne veux pas toucher, me faisait même sauter le contenu des balises <picture> que j'ai écrit moi-même, comme celle du logo svg du site, prévu pour changer de source en fonction du mode appliqué (clair ou sombre).

Pour éviter qu'il ne passe partout comme un bulldozer à défoncer tout le code que j'écris, je suis obliger d'ajouter des attributs spéciaux pour "protéger" les images que je veux gérer moi-même : eleventy:ignore

Je ne suis pas du tout fan de ce comportement de mâle toxique ("s'il dit pas non, c'est que c'est OK"). Non c'est pas ça le consentement !

Un plugin qui force sur le SVG

Ce forceur insiste pour passer sur mes SVG pour les remplacer par des jpeg et des webp sans intérêt. J'ai beau tout essayer, toutes les options listées, rien y fait…

Il va falloir que j'opt out à la main pour tous les SVG que j'insère, comme je l'ai fait pour le logo du site. (Fatigue ! 😩)

Information

Le problème de ce plugin et de sa doc

On commence à toucher du doigt un problème récurrent dans la doc de ce plugin : Si elle est très fournie, elle est parfois assez floue.

La description de certaines options n'est pas claire du tout, et sans exemples concrets montrant leur usage, on ne sait pas si on a juste mal compris ce qu'il étaient sensés faire ou si on a fait une erreur dans leur intégration.

Une image toute déformée

Au début mon image était toute déformée car la "moulinette magique" s'obstinait à placer un attribut height sur mes images, attribut calculé sur la largeur de la rendition maximale (à moins que ce ne soit la taille de l'original ?), alors que la largeur affichée était inférieure.

Résoudre cela m'a demandé beaucoup de tâtonnements, mais au final j'ai réussi à obtenir gain de cause en réunissant les deux conditions suivantes :

  • définir explicitement les dimensions voulues et activer le lazy-loading a semble-t-il déclenché l'ajout d'un attribut size="auto"
  • j'ai ajouté la propriété height: 100% à toutes les images

Avec ces deux éléments, l'image retrouve la hauteur qui convient en fonction de sa largeur d'affichage.

Manque de contrôle

En dehors de l'alternative textuelle et de l'ajout de quelques attributs basiques, je n'ai pas de contrôle sur le contenu de mes balises <picture>… du moins pas un contrôle fin.

Par exemple, je subis l'ajout de l'attribut height basé sur l'original et la "moulinette" assigne la plus petite résolution comme image par défaut. Ça veut dire que sur un navigateur assez ancien pour ne pas supporter source set, l'image 320px de large serait affiché en 720px de large (donc agrandi et fortement pixelisée)…

Conclusion : le tout magique, c'est pas pour moi

Non, je ne suis pas un control freak ! C'est juste que j'aime pas ne pas avoir le contrôle… hmm… 🤔

Bref ! Cette "moulinette magique" ne me convient pas, en particulier l'aspect où elle demande de la vigilance dès qu'on ajoute une image car on ne sait pas ce que ça va donner.

Je préfère un mode de fonctionnement moins "magique" et plus prévisible :

  • Si je ne précise rien, l'image est mise telle quelle (et donc sans optimisations)
  • Si je précise que je veux une optimisation, alors elle est traitée en conséquence

Cette approche augmente la charge côté auteurs et autrices (il faut penser à réclamer l'optimisation) mais au moins elle est sans surprises.

Nouvel objectif (pour demain) : creuser une autre méthode d'implémentation présentée dans la doc, les shortcodes Image(S'ouvre dans un nouvelle fenêtre)