Niveau : Expert

Les sprites CSS

Le temps des onmouseover, des images préchargées via JavaScript et des autres joyeusetés héritées des grandes périodes de tag soup est, comme le temps des Elfes de la Terre du Milieu, définitivement révolu : faire des effets de rollover sur des images est tout à fait possible en utilisant uniquement les CSS.

Tutoriel par (Développeur/Intégrateur web, France voisine de Genève)
Créé le , mis à jour le (252051 lectures)
Tags : css, rollover, sprite, sprites

La technique consiste à exploiter un fichier unique pour stocker de multiples images, positionnées les unes à côté des autres. Celles-ci seront ensuite appelées dans la feuille de style, et la fenêtre d'affichage sur l'une ou l'autre image sera définie en CSS grâce à la propriété background-position.

Attention : la technique exposée dans ce tutoriel est à manier avec précaution car elle peut engendrer des problèmes d'accessibilité (typiquement lorsque les images ne sont pas actives). Il est donc déconseillé de l'employer pour des images dont le contenu est pertinent (menu par exemple).

Les avantages des sprites CSS sont multiples :

  • cela permet de se passer totalement de JavaScript : il n'y a pas besoin de préchargement d'images ni de gestionnaire d'événements comme onmouseover pour déclencher l'effet survolé (en CSS),
  • dans les cas de recherche de performances ou d'optimisations pointues, quelques astuces permettent d'obtenir des résultats très intéressants en réduisant le nombre de requêtes au serveur pour afficher plusieurs images,
  • comme ces techniques se basent sur XHTML et CSS, tous les avantages de ces derniers utilisés à bon escient sont présents : séparation présentation/structure, code lisible et léger, accessibilité, etc.

Par exemple vous pourrez stocker de la sorte dans le même fichier les différents onglets d'un menu de navigation graphique, tel que celui qu'on retrouve sur La Gazette des Communes :

Sprites pour menu

Des sites à fort trafic (Youtube, Google, Facebook, Amazon, ...) exploitent cette technique sur des pages que vous consultez tous les jours :

Sprites sur grands sites

La technique des sprites CSS

Approche de base (sans sprite)

L'idée est de n'utiliser que XHTML/CSS pour mettre en forme un lien. En partant de l'idée somme toute logique que le lien en question possède une image en état normal et une autre en état survolé, voici ce que cela pourrait donner :

<!-- pour le XHTML -->
<a href="#" title="L'édito" id="editorial"><span>L'édito</span></a>
/** pour la CSS **/
#editorial{
  display:block;
  width:80px;
  height:31px;
  background:url(edito_normal.jpg) 0 0 no-repeat;
}
#editorial:hover,#editorial:active,#editorial:focus{
  background:url(edito_survole.jpg) 0 0 no-repeat;
}
#editorial span{
  text-indent: -5000px;
  display:inline-block;
}

La technique semble naturelle... seul souci, l'appel au fichier edito_survole.jpg se fait au moment du survol sur le lien. Il en résulte un "clignotement", ou un délai d'apparition sur certains navigateurs, selon le temps de chargement. Cette technique, bien que naturelle, est donc à oublier.

Principe (avec sprite)

C'est là que les sprites CSS entrent en jeu, l'idée va être la suivante :

Partie affichée de l'image de l'édito

  • les deux images de fond (état normal et état survolé) seront combinées en une seule,
  • seule la partie haute de l'image va être affichée grâce à background (ci-contre encadrée en rouge),
  • l'image de fond du lien sera simplement décalée lorsqu'on la survole avec la souris grâce à background-position.
<!-- pour le XHTML -->
<a href="#" title="L'édito" id="editorial"><span>L'édito</span></a>
/** pour la CSS **/
#editorial{
  display:block;
  width:80px;
  height:31px;
  background:url("editorial.jpg") 0 0 no-repeat;
}
/* on décale l'image de fond de la hauteur du lien, soit 31 px */
#editorial:hover,#editorial:active,#editorial:focus{
  background-position:0 -31px;
}
/* permet de cacher le texte du lien */
#editorial span{
  display:inline-block;
  text-indent: -5000px;
}

On affiche en background les premiers 31 pixels du haut de l'image (l'état normal du lien), et on décale l'image grâce à background-position pour faire apparaître celle en-dessous.

Le décalage étant instantané, l'illusion est parfaite, comme le montre l'exemple n°1.

Pousser la logique de la technique des sprites à l'extrême : quête de performance

Les extensions apôtres de la performance des sites que sont Google Page Speed et Yahoo Yslow vous l'indiqueront : il faut autant que possible minimiser les requêtes HTTP. Autrement dit, il faut essayer de charger le moins de fichiers possible. Certes les sprites CSS permettent de n'avoir qu'une image par lien au lieu de deux (état normal et survolé), mais cela fait quand même 10 fichiers images à charger si vous avez 10 liens... et donc 10 requêtes HTTP GET pour chaque image. Même si les connexions ADSL actuelles sont très performantes, il est légitime de penser à améliorer la vitesse de chargement sur les petites connexions, les Smartphones, etc. Le gain sera toujours appréciable entre le temps mis par le navigateur pour formuler la requête et l'affichage de l'image.

L'idée des sprites CSS peut alors être poussée à l'extrême : nous allons combiner les 10 images de liens d'un bandeau... en une seule.

<!-- pour le XHTML -->
<div id="bandeau">
<div id="menugauche">
 <ul>
  <li><a href="#" id="cv"><span>CV</span></a></li>
  <li><a href="#" id="edito"><span>Edito</span></a></li>
  <li><a href="#" id="skins"><span>Skins</span></a></li>
  <li><a href="#" id="newsweblog"><span>Weblog</span></a></li>
  <li><a href="#" id="realweb"><span>Réalisations</span></a></li>
 </ul>
</div>
<div id="menudroite">
 <ul>
  <li><a href="#" id="liens"><span>Liens</span></a></li>
  <li><a href="#" id="terragen"><span>Terragen 2</span></a></li>
  <li><a href="#" id="animations"><span>Animations</span></a></li>
  <li><a href="#" id="photonum"><span>Photo numérique</span></a></li>
  <li><a href="#" id="plansite"><span>Plan du site/Contact</span></a></li>
 </ul>
</div>
</div>

Construisons la CSS petit à petit, je ne mettrai que les propriétés réellement utiles à la compréhension, la totalité du code est donnée dans l'exemple.

#bandeau{
  background:url("top.jpg") 0 0 no-repeat;
  width:980px;
  padding:0;
}

La div bandeau prend donc l'image de fond, jusqu'ici rien de bien extraordinaire.

#menugauche span,#menudroite span{
  text-indent: -5000px;
  display:inline-block;
}
#menugauche ul,#menudroite ul{
  list-style-type:none;
  margin:0;
  padding:0;
  border:0;
}
#menugauche li,#menudroite li{
  margin:0;
  padding:0;
}

Ensuite, on fait disparaître les textes des liens pour n'afficher que les images de fond, et on fixe à 0 toutes les marges et autres paddings des divers éléments.

#menugauche li a,#menudroite li a{
  position:absolute;
  top:36px;
  left:30px;
  display:block;
  height:52px;
  width:92px;
  background:url("bandeau.jpg") no-repeat;
  margin:0;
  border:0;
}

Ici nous définissons les propriétés communes à tous les liens du bandeau, afin de factoriser la CSS : les liens seront donc positionnés en absolu, auront tous la même image bandeau.jpg en fond (état normal comme survolé, tous sont inclus dedans), auront tous la même taille et hauteur. Seul le positionnement des liens et de l'image de fond va différer pour chacun.

#menugauche #cv{
  background-position:2px 0;
}
#menugauche #cv:hover,#menugauche #cv:active,#menugauche #cv:focus{
  background-position:2px -52px;
}
#menugauche #edito{
  left:122px;
  background-position:-89px 0;
}
#menugauche #edito:hover,
#menugauche #edito:active,
#menugauche #edito:focus{
  background-position:-89px -52px;
}
/* etc... */

Le premier lien est positionné (il garde dejà les propriétés left:30px et top:36px définies plus haut dans les propriétés communes aux liens. Le suivant est décalé à droite et a son background décalé, l'effet de rollover étant aussi assuré par un décalage (celui du lien en état normal sur l'axe x, et le lien en état survolé sur l'axe y).

Le décalage étant toujours instantané, l'illusion est parfaite, comme on peut le constater sur l'exemple n°2.

Les avantages sont indéniables en termes de performances : une seule image permet de charger tous les liens de mon bandeau. Si vous généralisez le concept comme je l'ai fait pour intégrer le design de mon site dont sont tirés ces exemples :

  • Les liens du bandeau utilisent donc une seule image au lieu de 10 !
  • 12 images pour mes titres h1 sont combinées en une seule image, qui plus est, cette image se prête bien au format GIF : 26,2 Ko pour 12 titres en image, c'est plutôt appréciable !
  • Avantage supplémentaire : le temps de chargement du graphisme sera instantané pour les autres pages, l'image des h1 ayant déjà été chargée.
  • Au final, la totalité du graphisme se charge en seulement 6 images au lieu de 26.

Inconvénients

Néanmoins, la technique des sprites CSS poussée à l'extrême présente quelques inconvénients :

  • Si l'on doit changer une image d'un unique lien du bandeau, il faut rechanger et ré-exporter toute l'image au lieu d'une seule.
  • La feuille de style CSS sera un petit peu plus lourde.
  • Le positionnement peut être ardu si vous devez comme dans mon cas positionner au pixel près pour que les divers fonds soient parfaitement raccordés.

L'essentiel est de savoir à quel niveau s'arrêter : si le temps d'intégration et/ou de maintenance explose littéralement les bénéfices en termes de performances, autant revenir à la méthode classique. Bien entendu, ne se pose pas le problème de temps si vous faites votre intégration CSS à temps perdu. Toutefois, certains cas peuvent vous permettre de passer outre cette complexité... certaines fois en améliorant encore les performances de votre intégration. Jetons un oeil à ces astuces.

Autres pistes

Jouer avec les images de fond

Nous l'avons vu, le cas précédent d'intégration peut être difficile ou du moins un peu longuet. En travaillant sur une version en taille réduite de cette CSS, une idée simple m'est venu à l'esprit : positionner les liens du bandeau peut être pénible sans point de repère, donc pourquoi ne pas mettre les images des liens survolés directement dans l'image de fond ?

  • Déjà, cela permet d'avoir des points de repère pour positionner en absolu les éléments.
  • Ensuite, cela réduit le poids de l'image bandeau.jpg, qui ne contiendra que les images des liens en version normale, les images des liens survolés étant déjà contenues dans l'image de fond.
  • L'effet de rollover sera plus simple, il suffira de décaler l'image du lien normal pour faire apparaître ce qui sera caché derrière, à savoir l'image du lien en état survolé.

Voyons un exemple pratique (pour pouvoir grouper tous les exemples sur la même page, j'ai juste utilisé des classes et renommé les id, sinon le code reste le même) :

<!-- pour le XHTML -->
<div id="bandeau2">
<div id="menugauche2">
 <ul>
  <li><a href="#" class="cv2"><span>CV</span></a></li>
  <li><a href="#" class="edito2"><span>Edito</span></a></li>
  <li><a href="#" class="skins2"><span>Skins</span></a></li>
  <li><a href="#" class="newsweblog2"><span>Weblog</span></a></li>
  <li><a href="#" class="realweb2"><span>Réalisations</span></a></li>
 </ul>
</div>
<div id="menudroite2">
 <ul>
  <li><a href="#" class="liens2"><span>Liens</span></a></li>
  <li><a href="#" class="terragen2"><span>Terragen 2</span></a></li>
  <li><a href="#" class="animations2"><span>Animations</span></a></li>
  <li><a href="#" class="photonum2"><span>Photo</span></a></li>
  <li><a href="#" class="plansite2"><span>Plan du site</span></a></li>
 </ul>
</div>
</div>

Nous allons de nouveau construire la CSS petit à petit, je ne mettrai que les propriétés réellement utiles à la compréhension, la totalité du code est donnée dans l'exemple.

#bandeau2{
  background:url("itop.jpg") 0 0 no-repeat;
  width:480px;
  padding:0;
}

La div bandeau2 prend donc la nouvelle image de fond, de nouveau rien de bien extraordinaire.

#menugauche2 span,#menudroite2 span{
  text-indent: -5000px;
  display:inline-block;
}
#menugauche2 ul,#menudroite2 ul{
  list-style-type:none;
  margin:0;
  padding:0;
  border:0;
}
#menugauche2 li,#menudroite2 li{
  margin:0;
  padding:0;
}

De nouveau, on fait disparaître les textes des liens pour n'afficher que les images de fond, et on fixe à 0 toutes les marges et autres paddings des divers éléments.

#menugauche2 li a,#menudroite2 li a{
  position:absolute;
  top:2px;
  left:6px;
  display:block;
  height:52px;
  width:92px;
  background:url("ibandeau.jpg") no-repeat;
  margin:0;
  border:0;
}

Nous redéfinissons les propriétés communes à tous les liens du bandeau, afin de factoriser la CSS : les liens seront donc positionnés en absolu, auront tous la même image ibandeau.jpg en fond, auront tous la même taille et hauteur. Seul le positionnement des liens et de l'image de fond va différer pour chacun.

#menugauche2 li a:hover,
#menugauche2 li a:active,
#menugauche2 li a:focus,
#menudroite2 li a:hover,
#menudroite2 li a:active,
#menudroite2 li a:focus{
  background-position:0 800px;
}

Propriété commune à tous les liens survolés : il suffira juste de faire disparaître l'image, ce qui fera apparaître l'image en arrière-plan... laquelle contiendra l'image du lien survolé. J'ai volontairement exagéré le décalage de l'image de fond avec la valeur 800 px.

#menugauche2 .cv2{
  background-position:-6px -2px;
}
#menugauche2 .edito2{
  left:96px;
  background-position:-96px -2px;
}
/** etc. pour les liens de menugauche2 **/

Le premier lien est positionné : il garde dejà la propriété left:6px (top:2px étant commune à tous les liens de #menugauche2). Le suivant est décalé à droite, et a son background décalé aussi.

#menudroite2 li a{
  top:54px;
}

Là, nous définissons les propriétés communes aux liens de #menudroite2, à savoir leur position verticale.

#menudroite2 .liens2{
  background-position:-6px -54px;
}
#menudroite2 .terragen2{
  left:96px;
  background-position:-96px -54px;
}
/** etc. pour les liens de menudroite2 **/

Le principe reste le même, le premier lien est déjà positionné, et on décale les suivants ainsi que leur background.

Comme nous pouvons le voir sur l'exemple n°3, l'illusion de rollover est impeccable, et l'optimisation est encore meilleure : les images sont plus légères.

Combiner les propriétés CSS de couleurs et les images

Une autre façon d'optimiser votre intégration (avec ou sans sprite CSS d'ailleurs) est de savoir jouer avec les propriétés CSS et vos images, quand le design s'y prête. Si l'image de votre lien contient une bordure, et passe d'un fond noir à un fond gris en état survolé, pensez CSS : les bordures sont faciles à créer via CSS, et le fond se pilote aussi aisément. Si votre image pour le lien se prête à l'utilisation de la transparence, l'effet survolé et même une partie du lien peut être prise en charge par la CSS, ce qui va alléger d'autant les images contenant vos sprites.

Voyons un exemple simplifié, un unique lien :

<!-- pour le XHTML -->
  <div id="autremenu">
   <ul>
    <li><a href="#" id="autreaccueil" ><span>Home</span></a></li>
   </ul>
  </div>

L'image accueil.gif qui sera utilisée pour ce lien est sur fond transparent. Ainsi, ce sont les CSS qui vont prendre en charge l'effet de survol ainsi que la bordure.

#autremenu li a{
  display:block;
  width:91px;
  height:12px;
  margin:5px;
  background:#000 url("accueil.gif") 0 0 no-repeat;
  border:1px solid #444;
}
#autremenu span{
  text-indent: -5000px;
  display:inline-block;
}
#autremenu ul li a:hover,
#autremenu ul li a:active,
#autremenu ul li a:focus{
  background:#444 url("accueil.gif") 0 0 no-repeat;
}

Comme nous pouvons le voir sur l'exemple n°4, à partir de propriétés CSS, on peut obtenir des effets de rollovers sur images sans recourir à une deuxième image pour l'état survolé. Les images des sprites seront d'autant plus légères, vos sprites ne seront utilisés que pour les liens en état normal.

Conclusion

Nul doute que d'autres astuces combinant CSS et images sont possibles et que le tour de la question n'est pas encore fait. En tout cas, concilier une intégration CSS sans besoin de préchargement avec des contraintes de performances est à la portée de tout intégrateur souhaitant optimiser ses intégrations.

Pour rappel, une astuce récente proposait deux très bons outils pour générer automatiquement vos sprites.