Des sprites jusqu’à plus soif

Ceux qui ont utilisé les jeux vidéo il y a 10 ou 15 ans connaissent forcément les sprites. Pour économiser les ressources on regroupe toutes les icônes et les images dans un ou plusieurs fichiers. Le résultat c’est une sorte de tableau d’images, le jeu utilise alors simplement une partie de cette image à chaque fois qu’il a besoin d’une icône. 

Tout est une question de ressources disponibles, à l’époque on parlait de mémoire occupée et d’accès disque. Plusieurs années après, le Web a les mêmes problématiques : la poids et le nombre d’éléments à charger impactent directement et fortement les performances des pages. Il est donc logique qu’on utilise les mêmes solutions.

Pour le fonctionnement

Au lieu de gérer dix ou vingt images de fond dans vos feuilles de style, et autant d’icônes, on ne va plus gérer qu’une seule grosse image. Quand on veut une icône dans cette grande image on la référence avec ses coordonnées. Comme il n’y a plus qu’un seul fichier, il n’y a plus qu’une seule requête HTTP.

C’est de 30 à 100ms qui sont épargnés à chaque fois que vous agrégez une image avec une autre. Pour un jeu d’une quizaine d’icones c’est entre une demi seconde et une seconde que vous gagnez sur le rendu de vos pages. Et rappelez-vous à quoi ça sert, une demi-seconde c’est beaucoup, ça se verra sur vos ventes et votre public.

Mieux encore, contrairement à ce qu’on peut penser, l’image résultante sera quasiment tout le temps plus petite que la somme des deux premières, même si vous laissez de gros espaces transparents entre chaque.

Pour la technique                                 

Tout ça est on ne peut mieux expliqué sur pompage (qui traduit a list apart), css-tricks ou websiteoptimization. Mais pour faire un résumé, à partir de notre ancien code CSS et de nos cinq icônes de drapeaux…

a[lang] {
  padding-right: 20px ; 
  background-repeat: no-repeat ;
}
a:lang(fr) { background-image: url(drapeau-fr.png) ; }
a:lang(de) { background-image: url(drapeau-de.png) ; }
a:lang(uk) { background-image: url(drapeau-uk.png) ; }
a:lang(it) { background-image: url(drapeau-it.png) ; }
a:lang(es) { background-image: url(drapeau-es.png) ; }

…on peut faire une seule image qui contient un drapeau tous les 20 pixels, et remplacer le code CSS par le suivant :

a[lang] {
  padding-right: 20px ;
  background-repeat: no-repeat ;
  background-image: url(drapeaux.png) ;
}
a:lang(fr) { background-position:   0px 0px }
a:lang(de) { background-position: -20px 0px }
a:lang(it) { background-position: -40px 0px }
a:lang(es) { background-position: -60px 0px }
a:lang(uk) { background-position: -80px 0px }

Attention, si vous publiez des fichiers statiques sur un CDN et/ou que vous versionnez le fichier par son url, l’adresse du sprite changera à chaque fois que vous ajouterez quelque chose dedans. Faites donc en sorte de pouvoir changer d’un coup toutes les occurences dans votre feuille de style. Si le navigateur se voit obligé de télécharger plusieurs versions du même sprite pour une même page, ça met en échec tous les avantages du système.

Pour l’automatisation

Tout ça est assez simple. Il est juste un peu long de prendre son logiciel de traitement d’image pour placer au pixel près chaque image, puis générer le code CSS correspondant à chaque image. C’est là qu’arrive la magie d’Internet. Vous avez deux générateurs en ligne pour vos besoins, le premier sur website-performance.org et le second sur csssprites.com.

Mieux, le premier est téléchargeable chez vous si vous le souhaitez et Mathieu Pillard a un petit script python pour faire quelque chose de similaire. N’hésitez pas à recompresser ou vérifier la compression des fichiers obtenus.

Pour les détails

Vous trouverez des références qui vous proposent de faire des sprites horizontaux pour avoir une image plus légère en poids. Comme je l’ai expliqué dans un précédent billet, si vous utilisez PNG, un sprite vertical devrait vous donner des résultats similaires.

On va surtout utiliser l’horizontale pour des motifs qui se répètent en x, ou qui doivent pouvoir s’insérer dans des balise de taille indéfinie en largeur. Le vertical étant donc pour des motifs répétés en y ou avec une hauteur indéfinie et potentiellement grande. Quand vous pouvez borner la taille de la balise en largeur et en hauteur, et que vous n’avez pas besoin de répétition, vous pouvez disposer vos graphiques en tableau dans une grosse image.                 

Pour les inévitables problèmes

On dit souvent que rien n’est magique, qu’il y a toujours un contre-coup. Ici il est faible. On compte un seul bug, chez Opera. Il vous incitera à ne pas dépasser des positions qui au-delà 2042 pixels, ce qui est n’est pas une bien grosse contrainte.

Le second effet négatif est qu’une fois qu’une image est dans le sprite, la place est occupée à vie. Supprimer l’icône demande de tout décaler dans vos CSS, et la remplacer par une autre posera problème à cause des caches (certains voudront la nouvelle icône et risqueront d’avoir l’ancienne, et si quelques pages font encore référence à l’ancienne, ils auront la nouvelle.). N’hésitez donc pas à séparer vos graphiques en plusieurs fichiers suivant leur durée de vie ou leur thématique, pour pouvoir faire de temps en temps expirer tout le fichier et en recommencer un nouveau.

Pour aller plus loin

Pour l’avenir webkit a même prévu des propriétés CSS -webkit-mask. L’idée c’est de pouvoir définir une zone très précise à afficher, et pas simplement positionner l’image dans la balise HTML. On évitera ainsi de devoir isoler une icône simplement parce qu’elle doit apparaître au milieu d’une boite de 100 pixels de côté (avec les techniques actuelles soit on en fait une fichier isolé soit on laisse au moins 50 pixels de chaque côté de l’icône dans le sprite).

Sinon, toujours dans les spécificités des navigateurs, ceux qui utilisent Firefox peuvent mettre toutes leurs images dans un fichier jar et utiliser un pseudo protocole pour référencer une image à l’intérieur de cette archive. On garde la simplicité de plusieurs images, mais on télécharge tout en une seule requête HTTP.

Publié par edaspet

Plus d'informations sur mon profil en ligne

25 réponses sur « Des sprites jusqu’à plus soif »

  1. Cela évite les requêtes HTTP. Mais augmente la bande passante consommée. Surtout si l’on n’utilise pas toutes les images.

    La chose est donc intéressante si on a de nombreuses images dans la page et que l’on les utilise toutes (ou une grande partie).

  2. Disons que tu télécharges tout même si tu n’utilises pas tout. Mais en pratique le sprite consommera moins que la somme des images. Donc même si tu n’utilises pas tout, tu peux te retrouver en positif du pur point de vue de la bande passante.

    Ensuite c’est en effet un compromis entre la multiplication des requêtes HTTP et la fréquence d’utilisation des images.

    Si tu met bien une expiration explicite sur tes images statiques, que tes pages utilisent toutes une douzaines d’images de fond, il est probable que le gain sur les requêtes compense facilement la différence de poids due aux images non utilisées.

    Maintenant effectivement, si tu as peur de ne pas tout utiliser tu peux faire des sprites par thématiques ou par section.

    Mais dans l’ensemble, de ce que j’ai pu constater, en cas de doutes il y a plutot intérêt à aggréger qu’à séparer. C’est simplement du au fait que pour toute requête tu as au moins un aller-retour réseau de 30 à 100ms et 1ko entre les entêtes de requête et les entêtes de réponse, et 1ko encore pour le contenu de l’image.
    Au final le surcoût de bande passante pour faire passer d’un coup 10 icones dans l’image est assez faible sur le coût total de l’image initiale. Si tu n’utilises pas tout ce n’est pas très grave. Par contre si tu as besoin de lancer une nouvelle requête, là le surcoût est important.
    Bien voir aussi que ce sprite servira aussi aux futures requêtes vers le même site, donc peut être que les icones surnuméraires seront utilisées dans les autres pages.

  3. Très bon article, comme d’habitude. Juste une réserve, celle des générateurs de sprites. Je pense que des outils beaucoup plus automatisés vont sortir, par exemple http://smartsprites.osinski.name/. Tout est automatisé et on ne s’occupe plus de toute la partie css ni de l’agrégation des images.

    En tout cas d’après des tests effectués, c’est LE conseil performance qui permet de gagner beaucoup de temps.

    Les thèmes wordpress et autres vont sûrement s’y mettre et livrer une version avec un seul sprite css pour tout le thème.

  4. Tiens, ça c’est bigrement intéressant comme montage de générer ça directement à partir des déclarations CSS elles-même. C’est tellement simple que c’est ridicule de ne pas y avoir pensé.

    Reste tout de même qu’après il faut souvent repasser derrière l’outil pour afiner la compression, avant d’envoyer le fichier sur le CDN, donc l’url n’est pas forcément encore fixée pour l’outil. Ca n’est pas insurmontable ceci dit.

    Merci pour l’URL

  5. Merci pour l’explication détaillée, j’en avais entendu parler à plusieurs reprises, mais je ne m’étais jamais penché dessus de manière détaillée.

    Concernant le surcoût de la bande passante, c’est vraiment négligeable comparé au gain en termes de requêtes HTTP. Une requête HTTP, c’est un échange entre le client et le serveur, éventuellement le serveur doit faire un fork, il y a plusieurs accès disques (le fichier demandé, les .htaccess…), etc. Du coup, le plus souvent la phase de préparation du fichier prend plus de temps que le transfert en lui même. Il faut vraiment être dans un cas extrême pour que ça mette plus de temps (on parle des images de l’interface là, ce sont de petites images en général). Donc même si on gagne quelques millisecondes, en compressant mieux les fichiers, elles seront gâchées dès la première requête HTTP supplémentaire.

    Pour vous en convaincre, vous pouvez vous amuser avec le full page test de Pingdom, que je trouve assez génial : http://tools.pingdom.com/fpt
    Je ne sais pas si vous connaissiez ? Il montre de manière détaillée le chargement complet d’une page. Leur blog est intéressant aussi.

  6. En fait le cout d’une requete HTTP est surtout celui de la latence, plus que celui du fork et de la réaction coté serveur. Mais sinon oui, du point de vue client une requête HTTP coute cher, souvent bien plus que quelques octets sur une image.

  7. Oui, le fork ne coûte pas grand chose. Par contre un accès disque beaucoup plus. Le temps de préparation de la réponse côté serveur est flagrant lorsqu’on regarde les graphes de Pingdom. Par exemple pour la page d’accueil de Flickr : http://tools.pingdom.com/fpt/?url=http://www.flickr.com&treeview=0&column=objectID&order=1&type=0&save=true
    C’est tout se qui se trouve en vert. Le temps pris est à peu près équivalent à la latence (selon le site, il peut être inférieur ou supérieur).
    Cela dit, quelle que soit l’origine, on en arrive au même point : il vaut mieux un téléchargement un peu plus gros que plein de petits :o)

  8. Je viens de tomber sur un joli sprite: http://www.sproutcore.com/static/sproutcore/en/images/sc-theme-sprite.png

    Sinon, détail important, pour ceux qui se souviennent toujours du problème qu’a IE6 avec les images de fond qui clignotent. Pensez à activer le cache, j’ai pu observer mon design apparaissant tout lentement, deux images à la fois.. images qui sont bien évidemment les même. Ce qui n’était pas terrible pour des ombres portées (de la mort).

  9. Pingdom est pas terrible vu qu’il n’exécute pas le javascript.

    Le plus simple pour tester c’est YSlow, sinon un outil beaucoup mieux, assez récent, pagetest :
    http://pagetest.patrickmeenan.com:8080/

    C’est encore mieux que YSlow à mon avis (en tout cas au moins complémentaire car il utilise IE) car il affiche les vrais temps de réponse avec le détail au niveau du lookup DNS contrairement à YSlow, il fait des tests avec et sans cache, et en plus il est dispo online avec un client de test aux états unis.

  10. Potentiellement il y a aussi un problème lorsque l’ont ne laisse pas assez d’espace entre les images. En effet, si je grossi la taille des caractères les blocs ont tendance à grossir en hauteur (sauf utilisation d’em) et donc on peut se retrouver avec d’autres images que celles prévu qui apparaissent

  11. +1 avec goetsu.

    J’agrandis souvent les polices et urgl (comme on dit en bas-Ougrien).

    C’est plus qu’un problème seulement potentiel, c’est un problème réel qui peut même induire des difficultés de compréhension du message véhiculé par l’icône.

    J’aurais bien une saisie d’écran à fournir mais je ne sais pas si l’auteur du site en serait ravi.

    (waouh mon premier commentaire ici, je peux mourir en paix maintenant) 😉

  12. Wow, un site français focalisé sur les performances web ? Dire que je croyais être le seul français passionné par cet aspect du développement web.

    Je vais dévorer les autres billets, merci !

  13. l’idée est intéressante mais cotés mémoire pour les navigateurs ?
    En flash, un sprite, une fois dans la bibliothèque peut être utlisé 1 000 000 de fois sans nécessité d’avantage de mémoire mais sous IE, ou FF ou opéra, comment gère t’il l’image en mémoire si celle ci est utilisée à plusieurs endroits différents ? Quelqu’un sait ?
    On travail sur des IHM de plus en plus gourmand en mémoire avec les framework javascript etc… je trouve que ca a son importance.

  14. Sérieusement je ne sais pas. C’est à tester.
    Ce qui est sur c’est que même sur les petites machines c’est au final plus performant côté utilisateur. Donc que même si ça occupait plus de mémoire, c’est tout de même au bénéfice de l’utilisateur. Le bénéfice de l’utilisateur reste ce qui reste le plus important, et troquer une ressource chère contre une ressource pas chère est toujours pertinent.

  15. La gestion de la mémoire n’est pas le fort d’IE, et pour le confort de l’utilisateur on doit faire très attention aux problèmes de mémoire.
    Je travail sur des RI.A. et quand tu te retrouves avec un IE ou FF utilisant 2Go de mémoire après 20 minutes d »utilisation, je te jure que ca fait pas plaisir à l’utilisateur, surtout si au final on aboutit à un plantage du navigateur.
    Alors d’accord, on aura gagné quelques secondes au chargement de la page mais si c’est au prix d’un redémarrage du navigateur au bout de quelques minutes d’utilisation, ou pire un redémarrage de l’ordinateur, je doute que l’utilisateur apprécie ce petit bénéfice 🙂

  16. C’est une technique employée avec succès partout où je l’ai vue, sur IE aussi, sans faire ramer le navigateur. J’aurai donc tendance à dire « foncez ».

    Maintenant un test objectif n’est jamais de trop donc je t’invite à tester, et à nous faire des retours si tu vois quelque chose d’étrange. Je doute cependant que cela puisse être un problème dans ce cas précis.

  17. Anthony Ricaud me dit qu’il a posé la question à l’équipe webkit. D’après eux une image n’apparait qu’une seule fois en mémoire si elle est toujours référencée par la même URL.
    Il ajoute, et je suis d’accord, que c’est assez logique et qu’il serait étonnant que d’autres moteurs fonctionnent différemment.

    Reste donc le fait qu’on charge une grosse image pour potentiellement ne pas tout utiliser, mais là franchement c’est moi qui vais affirmer que si vous ne faites pas n’importe quoi, c’est plus que négligeables dans le cas qui nous préoccupe.

    (Note: Anthony n’arrive pas à envoyer son commentaire, je ne sais pas pourquoi. Si quelqu’un est dans la même situation, envoyez moi un mail)
    (qui n’arrive pas à poster de commentaire, je ne sais pas pourquoi

  18. Je n’affirme rien, je ne fais que poser la question… et en même temps je ne tiens pas compte de l’impression de chacun… si on s’en tenait à nos impressions on serait toujours à croire que la force de la gravité attire de la même manière une plume et un boulet de canon.
    Le seul moyen de le savoir serait d’utiliser deux classes heavy-cls (avec un image de 200ko) et light-cls (avec une image de 1px sur 1px) et d’afficher dans une page 99 et 1 … relever la mémoire pour IE, Opéra… et de switcher light-cls avec heavy-cls et regarder la nouvelle place mémoire utilisée. En 2009 on risque de se pencher sur le problème et à ce moment je ferais les tests et je vous tiendrais informé. Si quelqu’un a du temps pour tester ca avant… l’info m’intéresse 🙂 Merci

  19. Hello,

    Je m’étonne qu’Aurélien ou Stéphane n’aient pas relevé — au moins à titre d’avertissement — les gros problèmes d’accessibilité potentiels. Ils parlent du cas des images de fond utilisées comme illustration (ou permettant de préciser un intitulé, mais dans tous les cas on a un intitulé), et du risque de voir apparaitre plusieurs icônes pour un même item, par exemple. C’est effectivement un problème (pour ne pas parler des sprites sous la forme de grilles qui demandent d’utiliser un élément vide en HTML si on veut réellement placer une image comme icône sans afficher d’autres images plus à droite, sous le texte), mais c’est loin d’être le principal.

    Le principal problème, c’est l’utilisation d’éléments vides ou d’éléments au texte caché (liens et boutons sans intitulé ou liens et boutons dont on cache les intitulés en CSS) pour créer des boutons cliquables à partir d’une image de fond. Ce n’est pas un problème limité aux sprites, mais la technique des sprites incite très clairement à ce type de pratique. Il suffit de voir les images données comme exemple, dans lesquelles on retrouve souvent des boutons et icônes dont on peut supposer qu’ils seront utilisés sans texte HTML visible.

    La technique des sprites serait réellement intéressante si elle permettait d’éviter cet écueil, c’est à dire si on pouvait:

    1. utililiser l’élément IMG (avec attribut alt) pour afficher une portion d’image et pas l’image complète;
    2. utiliser un mécanisme HTML ou CSS standard pour dire «telle portion de telle image est un équivalent strict du contenu de tel élément».

    Vu qu’aucune de ces deux solutions n’existe, il y a un risque fondamental pour l’accessibilité des interfaces. Il appartient bien sûr à chaque intégrateur de faire les choses au mieux, mais vu les mythes sur les remplacement d’images accessibles largement répandus j’ai bien peur qu’attirer l’attention sur les sprites ne bénéficie aux performances qu’au détriment de l’accessibilité.

    Sinon, dans l’absolu, propriété content + data URI + CSS variables/constants, peut-être un coup à jouer à l’avenir?

  20. Déjà : uniquement pour les images décoratives. Ca ne concerne pas les images de contenu, et ça ne concerne que des images qui ne sont pas indispensables à la compréhension. Bref, de la décoration. Du coup pour moi le problème d’accessibilité est de fait limité.

    Il n’est pas question de cacher les intitulés pour les remplacer par un sprite (ce dont tu parles), mais d’ajouter une icone décorative à côté de cet intitulé pour aider l’utilisateur. Pas de problème spécifique d’accessibilité donc.

    Qu’on soit clair, je ne parle PAS de remplacement d’image ici. Et ton problème d’accessibilité est lié au remplacement d’image, pas au sprite.

    Sauf cas exceptionnel celui qui supporte CSS saura tirer profit des règles de positionnement de background donc ça ne posera pas de problème. Si tu te débrouilles bien, dans une majorité des cas tu peux trouver un moyen de positionner ton sprites pour que ça ne « casse » pas même si on augmente les taille de police. Bref, il y a des exceptions mais dans l’ensemble ça fonctionne sans dégats ici aussi.

    Après dans CSS3 il y a des moyens de découper l’image précisément (au lieu de simplement la positionner avec le risque de voir les autres icones à côté) et de remplacer le contenu de manière propre (sans aucun risque d’accessibilité). Mais … en CSS3, donc pas pour tout de suite.

  21. C’est effectivement un problème de mise en pratique plutôt qu’un défaut inhérent à la technique. Prenons par exemple ta présentation faite à l’AFUP, slide 38: tu reprends une image utilisée par Google Search (google.com, google.fr…), qui suggère très fortement une utilisation abusive. J’ai vérifié sur une page de résultat, et on a bien affaire dans certains cas à une technique de remplacement d’image, même si je dois avouer que dans l’ensemble ils ont très bien fait les choses. Je m’inquiète un peu pour le logo «Google Checkout» et certaines images de bouton (type fermeture de fausse fenêtre), par contre.

    Il reste que dans les recommandations sur l’utilisation des sprites mentionnent rarement la question de l’accessibilité. Voir par exemple: http://developer.yahoo.com/performance/rules.html#opt_sprites

    Du point de vue strict de la performance (au sens technique, pas commercial), utiliser les sprites y compris pour des boutons d’action sans intitulé fait parfaitement sens. Est-ce le rôle des publications sur la performance web de mentionner ce problème potentiel? Je pense que oui.

  22. je découvre ce blog… que ne trouve-je d’hab que des trucs en anglais qu’il faut déjà traduire ou comprendre dans le texte+ la technique c’est hard.
    les sprite le yslow le réclame à grands cris (ou c’est speed, en tout cas google
    http://pagespeed.googlelabs.com/ )
    si je comprends la technique et que j’ai vu les sites qui la font pour nous (quoique en vertical alors que c’est pas la 1er que je lis que c’est mieux en horizontal), je ne comprends toujours pas, une fois « armé » de tout, où on les met ? (question bête pour vous j’en suis sûre)

Les commentaires sont fermés.