Espace tutos dev’ & informatique 🐧

Images data-uri et génération en PHP

Publié le
Article sur PHP et l'optimisation des images data uri

Introduction

Les images sont en grande partie responsables de l’attractivité d’une page web. On les retrouve donc un peu partout, de l’entête au pied de page en passant par les puces, flèches et bordures. L’utilisation de toutes ces images a un coût cependant : il faut que le navigateur les télécharge, ce qui au mieux causera un espace vide temporaire dans la page, et au pire retardera l’affichage de la page toute entière.

Différentes techniques, telles que le lazy-loading, la mise en cache et l’utilisation du format WEBP, existent pour pallier à ce problème. Dans cet article, nous allons voir la technique des data-uri dans les balises d’image, et comment les générer depuis du code PHP.

Note : le code a été testé avec PHP 7.3, mais devrait être compatible dès PHP 5.3. Côté navigateur, il fonctionne comme d’habitude à partir de Internet Explorer version 11.

A propos des images data-uri

Des données, sous forme de texte

La technique des images data-uri consiste à insérer directement dans la page les données de l’image, sous une forme que le navigateur est capable d’interpréter. Les données sont insérées à la place de l’adresse de l’image, c’est à dire dans l’attribut SRC dans le cas d’une balise d’image. Voici trois exemples de balises qui affichent la même image, dont les deux dernières utilisent la technique data-uri :

<img src="/wp-content/uploads/2021/03/fleche-haut-gauche-noire.svg" />
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA4NSAyOC4wNCI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiMzMzM7fTwvc3R5bGU+PC9kZWZzPjx0aXRsZT5GaWNoaWVyIDM8L3RpdGxlPjxnIGlkPSJDYWxxdWVfMiIgZGF0YS1uYW1lPSJDYWxxdWUgMiI+PGcgaWQ9IkNhbHF1ZV8xLTIiIGRhdGEtbmFtZT0iQ2FscXVlIDEiPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTguMjEuMzNDOCwuMjcsNy44OC4xMiw3LjcuMDhhMS40MywxLjQzLDAsMCwwLTEuMTEuMywxLjk0LDEuOTQsMCwwLDAtMi4xMywxQTM3LjA4LDM3LjA4LDAsMCwwLDAsMTQuMjdjLS4zNSwzLjEsNC4zOSwzLjQ3LDUuMjYuNzEuMzItMSwuNjEtMi4wOC44OC0zLjEzYTIxLjY3LDIxLjY3LDAsMCwwLDQuNjIsOC4zNGM1LDUuMzMsMTIuNTQsNy41NiwxOS42Niw3LjgyLDkuMTIuMzQsMTguMjgtMi4xMiwyNy4xNi0zLjg3LDQuNjItLjksOS4yNi0xLjc2LDEzLjg2LTIuNzYsNC4yLS45Miw5LTEuNDcsMTIuODYtMy4yOSwxLjMyLS42Mi41MS0yLjQyLS42OS0yLjUyLTMuNTgtLjMtNy40Ni42Ny0xMSwxLjE1LTMuOTEuNTMtNy44LDEuMTctMTEuNjksMS44LTcuNzQsMS4yNS0xNS41MiwyLjg3LTIzLjM0LDMuNi02LjY5LjYzLTEzLjg4LjI1LTE5LjYzLTMuNjNhMTQuNjQsMTQuNjQsMCwwLDEtNS43NS03LjRBOC40Miw4LjQyLDAsMCwxLDEyLDkuNjNhMzYuOTIsMzYuOTIsMCwwLDAsMi41NCwyLjc1YzIuNjgsMi40OSw2LjYxLTEuNDEsNC00LTEuNjctMS42OC0zLjYtMy4xNS01LjQtNC42OC0uNDEtLjM1LS44Ni0uNzItMS4yOC0xLjA5YTEuOTQsMS45NCwwLDAsMSwwLS4yNEMxMiwuMTQsOS43OS0uNDUsOC4yMS4zM1oiPjwvcGF0aD48L2c+PC9nPjwvc3ZnPiA=" />
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 85 28.04'%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill:%23333%3B%7D%3C/style%3E%3C/defs%3E%3Ctitle%3EFichier 3%3C/title%3E%3Cg id='Calque_2' data-name='Calque 2'%3E%3Cg id='Calque_1-2' data-name='Calque 1'%3E%3Cpath class='cls-1' d='M8.21.33C8,.27,7.88.12,7.7.08a1.43,1.43,0,0,0-1.11.3,1.94,1.94,0,0,0-2.13,1A37.08,37.08,0,0,0,0,14.27c-.35,3.1,4.39,3.47,5.26.71.32-1,.61-2.08.88-3.13a21.67,21.67,0,0,0,4.62,8.34c5,5.33,12.54,7.56,19.66,7.82,9.12.34,18.28-2.12,27.16-3.87,4.62-.9,9.26-1.76,13.86-2.76,4.2-.92,9-1.47,12.86-3.29,1.32-.62.51-2.42-.69-2.52-3.58-.3-7.46.67-11,1.15-3.91.53-7.8,1.17-11.69,1.8-7.74,1.25-15.52,2.87-23.34,3.6-6.69.63-13.88.25-19.63-3.63a14.64,14.64,0,0,1-5.75-7.4A8.42,8.42,0,0,1,12,9.63a36.92,36.92,0,0,0,2.54,2.75c2.68,2.49,6.61-1.41,4-4-1.67-1.68-3.6-3.15-5.4-4.68-.41-.35-.86-.72-1.28-1.09a1.94,1.94,0,0,1,0-.24C12,.14,9.79-.45,8.21.33Z'%3E%3C/path%3E%3C/g%3E%3C/g%3E%3C/svg%3E" />
Flèche haut gauche
Dans chaque exemple, il s’agit de cette flèche

On peut aussi utiliser les données dans du style CSS, comme ci-dessous :

div{
  background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA4NSAyOC4wNCI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiMzMzM7fTwvc3R5bGU+PC9kZWZzPjx0aXRsZT5GaWNoaWVyIDM8L3RpdGxlPjxnIGlkPSJDYWxxdWVfMiIgZGF0YS1uYW1lPSJDYWxxdWUgMiI+PGcgaWQ9IkNhbHF1ZV8xLTIiIGRhdGEtbmFtZT0iQ2FscXVlIDEiPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTguMjEuMzNDOCwuMjcsNy44OC4xMiw3LjcuMDhhMS40MywxLjQzLDAsMCwwLTEuMTEuMywxLjk0LDEuOTQsMCwwLDAtMi4xMywxQTM3LjA4LDM3LjA4LDAsMCwwLDAsMTQuMjdjLS4zNSwzLjEsNC4zOSwzLjQ3LDUuMjYuNzEuMzItMSwuNjEtMi4wOC44OC0zLjEzYTIxLjY3LDIxLjY3LDAsMCwwLDQuNjIsOC4zNGM1LDUuMzMsMTIuNTQsNy41NiwxOS42Niw3LjgyLDkuMTIuMzQsMTguMjgtMi4xMiwyNy4xNi0zLjg3LDQuNjItLjksOS4yNi0xLjc2LDEzLjg2LTIuNzYsNC4yLS45Miw5LTEuNDcsMTIuODYtMy4yOSwxLjMyLS42Mi41MS0yLjQyLS42OS0yLjUyLTMuNTgtLjMtNy40Ni42Ny0xMSwxLjE1LTMuOTEuNTMtNy44LDEuMTctMTEuNjksMS44LTcuNzQsMS4yNS0xNS41MiwyLjg3LTIzLjM0LDMuNi02LjY5LjYzLTEzLjg4LjI1LTE5LjYzLTMuNjNhMTQuNjQsMTQuNjQsMCwwLDEtNS43NS03LjRBOC40Miw4LjQyLDAsMCwxLDEyLDkuNjNhMzYuOTIsMzYuOTIsMCwwLDAsMi41NCwyLjc1YzIuNjgsMi40OSw2LjYxLTEuNDEsNC00LTEuNjctMS42OC0zLjYtMy4xNS01LjQtNC42OC0uNDEtLjM1LS44Ni0uNzItMS4yOC0xLjA5YTEuOTQsMS45NCwwLDAsMSwwLS4yNEMxMiwuMTQsOS43OS0uNDUsOC4yMS4zM1oiPjwvcGF0aD48L2c+PC9nPjwvc3ZnPiA=');
}

Syntaxe et encodages

Les images insérées en data-uri sont en général encodées de deux façons :

  • Elles peuvent être encodée sous forme Base64, qui convertit les données binaires en texte incompréhensible (pour nous). Il s’agit du deuxième exemple. Cela peut être appliqué à tous les formats : images JPEG, PNG, GIF, SVG, etc.
  • L’autre encode les données au format texte URL (similaire à celui utilisé pour l’encodage des données dans les requêtes GET), qui permet de conserver un texte lisible. Il s’agit du troisième exemple. Il n’est applicable qu’aux images dont le contenu est sous forme de texte : le format SVG.

Le type MIME de l’image est également à indiquer, car le navigateur ne peut pas le deviner tout seul. Les types les plus utilisés sont :

Format du fichier imageType MIME
Image JPEGimage/jpeg
Image PNGimage/png
Image GIFimage/gif
Image SVGimage/svg+xml

Pour synthétiser, la syntaxe est la suivante :

<!-- data-uri basé sur le Base64 : compatible avec toutes les images -->
<img src="data:<type mime>;base64,<contenu en base64>" />

<!-- data-uri basé sur le texte encodé URL : compatible avec les images SVG -->
<img src="data:<type mime>,<contenu encodé texte URL>" />

Compatibilité des navigateurs

Pour ce qui est des data-uri au format Base64, la compatibilité est là depuis Internet Explorer 8, avec une restriction de 32 kilo-octets comme taille de données. A partir d’Internet Explorer 9 la limite passe à 4 giga-octets, ce qui devrait être plus que suffisant.

Pour le format texte URL il est nécessaire d’encoder tous les caractères dans leur équivalent URL, à part quelques uns : les lettres, les nombres, les points, les virgules, les apostrophes, les deux-points, les signes égal et slash. Les langages de programmation ont en général une fonction pour cela, comme encodeURIComponent() en javascript et rawurlencode() en PHP. La compatibilité est dictée par le support des navigateurs pour le format SVG, ca devrait aller à partir d’Internet Explorer 11.

Les images SVG, qu’elles soient sous forme Base64 ou texte URL, doivent respecter certaines contraintes afin de pouvoir être affichées dans tous les navigateurs. Ces dernières seront évoquées dans la section concernant la génération en PHP des data-uris.

Avantages et inconvénients

Les avantages concernent l’optimisation du rendu de la page :

✔︎  Pas de requête supplémentaire pour récupérer l’image, donc un temps de rendu global amélioré
✔︎  Si l’image est en haut de page, cela peut réduire le décalage du contenu au rendu (CLS) et booster le score Page Speed Insights
✔︎  On évite le lazy loading, qui cause un blanc et peut ralentir le défilement de la page s’il y a de nombreuses images à charger

Côté inconvénients, on effectue des compromis :

✘︎  Le poids de la page augmente. Une image au format Base64 peut être 33% plus lourde que sa source, et une image en texte URL peut être plus lourde encore en fonction de son contenu
✘︎  Pas de mise en cache. Si l’image est utilisée sur plusieurs pages, utiliser le cache navigateur est préférable
✘︎  Pas de nom de fichier. Pour le SEO, on devra se contenter de l’attribut ALT de la balise d’image
✘︎  Inclure directement les données rend le code source de la page moins lisible
✘︎  Générer les données dynamiquement augmente la charge serveur

Personnellement, je limite l’utilisation aux images légères (autour de 1 kilo-octet), avec une tolérance pour les images un peu plus lourdes si elles sont en haut de page et ont un impact sur le CLS. J’utilise le format texte URL pour les images SVG et Base64 pour le reste.

Génération des data-uri des images

Conversion à l’aide un outil en ligne

Sur un petit projet, il est possible de copier coller directement la data-uri dans le code de la page. C’est peu lisible, mais si l’image est suffisamment légère cela ne devrait pas poser trop de problèmes.

Pour cela, on peut utiliser par exemple l’outil web gratuit situé ici : https://dopiaza.org/tools/datauri/index.php. Il ne faut pas oublier de cocher la case concernant la Base64 pour les images binaires, pour le reste l’utilisation est simple et permet soit d’uploader un fichier, utiliser l’adresse d’un fichier déjà en ligne ou copier/coller un contenu (utile pour les images SVG par exemple).

Ensuite, il ne reste qu’à ajouter dans le code de la page la balise d’image avec l’attribut SRC utilisant la data-uri. Les attributs LOADING et SRCSET peuvent être retirés.

<img width="50" height="50" alt="Texte SEO de l'image" src="data:image/png;base64,..." />

Conversion à l’aide d’une fonction PHP

Sur un site à base de PHP, on peut aussi générer directement la data-uri lors de la requête de la page par le visiteur, cela permet de conserver un code source propre, au détriment de la charge du serveur. L’utilisation d’un cache serveur HTTP est recommandée, ainsi la page avec les données n’est générée qu’une fois, puis servie à tous les visiteurs.

Voici la fonction que je vous propose pour la conversion d’un fichier image vers une data-uri. Elle est plus complexe que ce qui est proposé ailleurs, mais c’est pour la bonne cause, promis ! Plus bas il y a un exemple d’appel à cette fonction.

/**
 * Retourne la représentation data-uri du fichier image spécifié.
 * @param string $path Chemin du fichier
 * @param boolean $forceBase64 Toujours utiliser la forme Base64, utilisé uniquement pour les SVGs
 * @return string|boolean La chaine data-uri, ou false en cas d'erreur
 */
function imageDataUri($path, $forceBase64=false){

  // Vérifie si le fichier est lisible
  if(! $path || ! @is_readable($path)) return false;

  // Lit le contenu du fichier
  $data = file_get_contents($path);
  if($data === false) return false;

  // Supprime le marqueur utf8-bom du contenu s'il est présent
  if("\xEF\xBB\xBF" == substr($data, 0, 3)) $data = substr($data, 3);

  // Détermine le type MIME du contenu
  $finfo = finfo_open(FILEINFO_MIME_TYPE);
  if(! $finfo) return false;
  $mime = finfo_buffer($finfo, $data);
  finfo_close($finfo);
  if(! $mime) return false;
  
  // Correction du type MIME dans certains cas
  if($mime == "image/svg") $mime = "image/svg+xml";
  if($mime == "text/xml") $mime = "image/svg+xml";

  // Correction du code SVG si nécessaire
  if($mime == "image/svg+xml"){
    if("<svg" != substr($data, 0, 4)) $data = substr($data, strpos($data, "<svg"));
    if(strpos($data, "http://www.w3.org/2000/svg") === false) $data = str_replace("<svg", "<svg xmlns=\"http://www.w3.org/2000/svg\"", $data);
  }

  // Génération data-uri en texte URL
  if($mime == "image/svg+xml" && ! $forceBase64){
    $data = trim($data);
    $data = preg_replace("/\\s+/", " ", $data);
    $data = preg_replace("/\"/", "'", $data);
    $data = rawurlencode($data);
    $data = str_replace(array("%20", "%27", "%2C", "%3D", "%3A", "%2F"), array(" ", "'", ",", "=", ":", "/"), $data);

    $result = "data:".$mime.",";
    $result .= $data;
    return $result;
  }

  // Génération data-uri en Base64
  $result = "data:".$mime.";base64,";
  $result .= base64_encode($data);
  return $result;
  
}
<img width="50" height="50" alt="Texte SEO de l'image" src="<?php echo imageDataUri('img/exemple.png') ?>" />

Ce qui est intéressant dans cette fonction c’est son support amélioré du format SVG. Comme indiqué précédemment, pour avoir un affichage sur les navigateurs récents des data-uri des images SVG il faut respecter plusieurs contraintes, notamment :

  • Ne pas avoir d’entête <?xml /> au début
  • Ne pas avoir d’indicateur de BOM au début
  • Avoir la déclaration xmlns sur l’élément racine du SVG
  • Et en cas d’utilisation de la forme texte URL : encoder tous les caractères dans leur équivalent URL, à part quelques uns : les lettres, les nombres, les points, les virgules, les apostrophes, les deux-points, les signes égal et slash

Elle gère tout ceci, ainsi que la correction des types MIME invalides parfois retournés par PHP, text/xml et image/svg, au lieu de image/svg+xml. Il est aussi possible de forcer l’utilisation de la forme Base64 en passant true en second paramètre.

Conclusion

Voila, nous en savons un peu plus sur les images data-uri et leur génération en PHP. Elles répondent à un problème spécifique d’optimisation et ne doivent être utilisées qu’avec parcimonie, mais permettent tout de même de gagner quelques millisecondes dans le rendu de la page, ce qui peut parfois faire la différence.

Bon code, à bientôt.

Références

CanIUse – Format data-uri
CanIUse – Images SVG
Dopiaza – Convertisseur en ligne data-uri
Yoksel – Encodeur SVG au format URL en ligne
Tigt – Mini SVG data uri (Github)

Photo de profil de Loïc Burtin Code Colibri développeur web à Dijon

A propos de l'auteur

Loïc Burtin

Développeur web indépendant, avec une tendance à couper les plumes en quatre pour que les sites se chargent plus rapidement.

Partagez cet article  =>