Sprites CSS : La mort du découpage d’image

novembre23, 2007

Article de Dave Shea paru en version originale sur le site www.alistapart.com, traduction par mes soins.

Designer graphique dont le travail a été largement reconnu et récompensé par de nombreux prix, membre du Web Standard Project (WaSP), Dave Shea est le créateur et le « jardinier » du site CSS Zen Garden. En plus de son poste à la tête de la Bright Creative, une agence de design web, il écrit régulièrement sur tout ce qui a trait au Web sur son site mezzoblue.com.

Quand les jeux videos étaient encore marrant (on parle ici des jours de gloire du 8-bits), les graphismes étaient beaucoup plus simple par nécéssité. Les personnages 2D et les paysages étaient dessinés individuellement, tout comme dans l’art numérique résurgent aujourd’hui. Des centaines, et plus tard des milliers de petits graphiques appelés « Sprites » étaient les briques de tout élément visuel d’un jeu.

Exemple de Sprites

À mesure que la complexité de jeu augmentait, les techniques se sont développées pour contrôler la multitude de sprites tout en préservant la fluidité du jeu. Une de ces variations fut de dessiner tous les sprites dans une image unique, puis de découper et positionner à la demande chaque graphique afin de les dessiner dans le bon ordre à l’écran.

Quel est le rapport avec le web ?

Tout ce qui est dépassé redevient utile, et malgré l’essort des jeux 3D, qui a rendu obsolète les cartes de sprites, l’émergence des appareils mobiles et de leurs capacités limitées aux jeux 2D a remis ces même cartes de sprites au goût du jour. Et maintenant, avec un peu de maths et beaucoup de CSS, on va reprendre la base de ce concept et l’appliquer au monde du web design.

En fait, nous allons remplacer le découpage et le tronçonnage d’image « vieille-école » (et tout le JavaScript qui va avec) par une solution en CSS. Et grâce à la manière dont fonctionne le CSS, on peut aller encore plus loin : en construisant une grille d’images et en concevant un moyen d’afficher individuellement chaque cellule de cette grille, on peut stocker tous les boutons/items de navigation/ce qu’on veut dans un seul fichier image principal, ou bien des images « avant/après » pour des liens.

Comment fonctionnent les Sprites CSS ?

Evidemment, les outils de base pour faire fonctionner tout ça sont déjà implémentés en CSS, il suffit juste d’un peu d’imagination.

Commençons par l’image principale en elle-même. On va diviser un rectangle en quatre zone. Vous remarquerez que dans cette image on met les états « avant » en haut, et les états « après » (:hover) juste en dessous. Il n’y a pas de division franche entre les différents liens pour le moments, alors imaginez pour l’instant que chaque partie de texte est un lien. (Pour des raisons de simplicité, on continuera de se référer aux images de lien comme image « avant », et aux images d’état :hover comme image « après » pour le reste de l’article. Il est possible d’étendre cette méthode aux états :active, :focus et :visited, mais nous n’irons pas jusque là.)

Les personnes familières de la technique du Rollover CSS de Petr Stanicek’s (Pixy) voient déjà où l’on veut en venir. Cet article doit beaucoup à l’exemple de Pixy sur la fonction basique dont nous allons parler. Mais n’allons pas trop vite.

Voyons le code HTML. Tout bon truc CSS permet de poser une couche graphique sur un bloc de code propre, et cette technique ne fait pas exception :

<ul id="skyline">
<li id="panel1b"><a href="#1"></a></li>
<li id="panel2b"><a href="#2"></a></li>
<li id="panel3b"><a href="#3"></a></li>
<li id="panel4b"><a href="#4"></a></li>
</ul>

Ce code va servir de base pour notre exemple. Léger, des balises simples qui s’affichent bien en environnement dégradé (ancien navigateur ou navigateur sans CSS), c’est là toute sa force, et cette tendance est bonne pour le métier. C’est un idéal à viser. (On ne gèrera pas le texte des liens dans cet article. Vous appliquerez plus tard votre technique favorite de remplacement de texte par une image pour cacher le texte que vous ajouterez.

Appliquer le CSS

Après ce premier bloc basique, il est temps de construire le CSS. Un petit mot avant de commencer : en raison d’un problème d’IE, on posera l’image « après » par dessus l’image « avant », au lieu de remplacer l’une par l’autre. Le résultat ne comportera aucune différence si on aligne précisemment les images, mais cette méthode permet d’éviter un effet de scintillement.

#skyline {
  width: 400px;height: 200px;
  background: url(test-3.jpg);
  margin: 10px auto;
  padding: 0;
  position: relative;
}
 
#skyline li {
  margin: 0;
  padding: 0;
  list-style: none;
  position: absolute;
  top: 0;
}
 
#skyline li, #skyline a {  height: 200px;
  display: block;
}

Contrairement à ce qu’on pourrait penser, on n’affecte pas l’image « avant » aux liens, mais plutôt à la balise <ul>. Vous comprendrez pourquoi dans un moment.

Le bout de CSS dans l’exemple ci-dessus règle des choses comme la dimension du bloc #skyline et des éléments de liste, le positionnement des éléments de liste, et retire les puces de la liste.

On laisse les liens en leur état de blocs vides et transparents (mais aux dimensions spécifiques) pour déclencher le lien, et on les positionne en utilisant les conteneurs <li>. Si on positionnait les liens eux même en ignorant les <li>, cela déclencherait des erreurs dans les navigateurs les plus anciens, donc on va éviter ça.

Positionner les liens

Les balises <li> sont positionné en absolu, alors pourquoi ne s’affichent elles pas en haut de la fenêtre du navigateur ? Eh bien, une des propriétés, bizarre mais très utile, des éléments positionnés est que tous leurs descendant vont baser leur position absolu non pas sur la fenêtre du navigateur, mais sur leur premier ancêtre positionné. Le résultat est que tant qu’on applique une position: relative; à #skyline, alors la position absolu des balises <li> sera calculée à partir du coin en haut à gauche de #skyline.

#panel1b {left: 0; width: 95px;}
#panel2b {left: 96px; width: 75px;}
#panel3b {left: 172px; width: 110px;}
#panel4b {left: 283px; width: 117px;}

Ainsi, #panel1b est positionné contre le bord gauche de #skyline, #panel2b est positionné à 96px du bord gauche de #skyline, et ainsi de suite. Dans le listing précédent, on a assigné la valeur display: block; aux liens, ainsi que la même hauteur qu’aux balises <li>, si bien que les liens vont totalement remplir ces balises <li>, ce qui est exactement ce qu’on veut.

Nous avons maintenant une image basique découpée en liens, mais pas d’état :hover. Regardez cet exemple. Il sera sûrement plus simple de constater le résultat en activant les bordures.

Hover

Par le passé nous aurions mis un peu de JavaScript afin d’échanger les images « avant/après ». Au lieu de cela, les deux états étant dans la même image, tout ce qu’il nous faut trouver est un moyen de retirer de manière sélective chacun de nos états pour le lien approprié.

Si on applique l’image principale à l’état :hover sans valeurs additionnelles, on affichera uniquement le coin supérieur gauche (ce qui n’est pas ce qu’on veut) coupé à la taille du lien (ce qui est ce qu’on veut). On doit donc modifier la position de l’image d’une manière ou d’une autre.

On doit y arriver avec les seules valeurs connues en pixels ; un peu de maths devraient nous permettre de décaller cette image de fond à la fois horizontalement et verticalement afin de ne montrer que la partie correspondant à l’état « après ».

C’est exactement ce qu’on fait :

#panel1b a:hover { background: transparent url(test-3.jpg) 0 -200px no-repeat;}
#panel2b a:hover { background: transparent url(test-3.jpg) -96px -200px no-repeat;}
#panel3b a:hover { background: transparent url(test-3.jpg) -172px -200px no-repeat;}
#panel4b a:hover { background: transparent url(test-3.jpg) -283px -200px no-repeat;}

Où a-t-on trouvé ces valeurs en pixel ? On ne va pas le cacher : la première valeur est bien évidemment le décalage horizontal (en partant du bord gauche), et la deuxième le décalage vertical.

Toutes les valeurs verticales sont les même : étant donné que l’image principale fait 400px de haut, et que notre état « après » commence à l’exact milieu, on a simplement divisé la hauteur en deux. Décaler l’image de fond vers le haut de 200px signifie qu’on applique une valeur négative. Pensez que le haut du lien est le point de départ, ou 0. Pour positionner l’image de fond à 200px au dessus de ce point, il est sensé de bouger le point de départ de -200px.

De même, si le côté gauche de chaque lien correspond aussi à 0, on doit décaler l’image de fond de la largeur additionné de chaque balise <li> précédent le lien sur lequel on travaille. Donc, le premier lien ne nécessite pas de décalage, puisque qu’il n’y a pas de pixel avant son point de départ horizontal. Le deuxième lien requiert un décalage de la largeur du premier, le troisième lien requiert un décalage de la largeur combinée des deux premiers, et le dernier requiert un décalage de la largeur combinée des trois précédents liens.

C’est un peu lourd à expliquer comme procédé, mais le fait de jouer un peu avec ces valeurs va rapidement vous montrer comment marche le décalage, et une fois familier de son fonctionnement, vous verrez que ce n’est pas si compliqué à faire.

Et voilà ! Tout vos liens en roll-over avec une seule et même image, réduite en HTML à une simple liste.

Les boutons

Il n’y a aucune raison pour que les liens soient collés, côte-à-côte comme dans l’exemple précédent. Une image avec des zones cliquables peut être utile dans certains cas, mais pourquoi ne pas séparer chaque lien dans son propre bouton autonome ? Ainsi, on peut ajouter des bordures et des marges à chacun, et de manière générale, les traiter aussi séparemment selon nos besoins.

En fait, les briques sont déjà en places. Nous n’avons pas vraiment besoin de modifier notre code de manière radicale ; le changement principal est de créer une image qui ne soit pas continue de lien en lien, comme c’était le cas dans l’exemple précédent. Puisque l’on ne peut pas compter sur la balise <ul> pour placer notre image de fond, on va l’appliquer sur chaque balise <li> en décalant de manière différente pour chaque, comme on a décalé pour l’état « après » dans l’exemple précédant.

Avec une image appropriée et un peu d’espacement entre chaque <li>, on obtient des boutons.

Notez que dans cet exemple, on a ajouté des bordures de 1px qui, bien sûr, s’ajoutent à la largeur finale de notre lien. Cela affecte nos valeurs de décalage ; on compense ceci en ajoutant un décalage de 2px aux endroits appropriés.

Les formes irrégulières

Jusqu’à présent, nous nous sommes uniquement concentrés sur des formes rectangulaires, qui ne s’entrecroisent pas. Mais qu’en est il d’images cliquables plus complexes que les tronçonneurs d’image comme Fireworks ou ImageReady exportent si facilement ? Relax, on couvre ça aussi.

On commence de la même manière que le premier exemple, en appliquant l’image de fond sur la balise <ul>, en supprimant les puces de la liste, en fixant les tailles, etc. La grosse différence vient de la manière dont on positionne les balises <li> ; le but étant d’encadrer chaque élément avec une boite aux bordures très ajustées.

Une fois de plus, grâce à la possibilité d’utiliser une positionnement absolu relatif au coin en haut à gauche de la balise <ul>, nous sommes capable de placer nos liens exactement où l’on veut qu’ils soient. Maintenant, tout ce qu’il nous reste à faire est de d’afficher l’état « après ».

Malheureusement, dans ce cas, une simple image « avant/après » ne suffit pas. À cause de l’entrelacement des objets, se reposer sur un seul état « après » ferait apparaître des morceaux d’objets environnants également dans un état « après ». En fait, cela montrerait précisémment les morceaux entremêlés aux bordures du lien. (Plus facile à voir qu’à lire.)

Comment éviter ça ? En ajoutant un deuxième état « après », et en sélectionnant soigneusement quels objets vont où. L’image principale à dans ce cas mis les objets bleu et pourpre dans le premier état « après », et les objets vert, orange et jaune dans le second. Cet ordre permet que deux objets entrelacés et adjacents ne soient pas sur la même image « après ». Ainsi, l’illusion est complète.

Avantages et inconvénients

Quelques remarques en guise de conclusion. Notre nouvelle méthode de Sprites CSS passe bien sur la plupart des navigateurs récents. La seule exception notable est Opera 6, qui n’applique pas l’image de fond sur le lien à l’état :hover. Pourquoi, ce n’est pas sûr, mais il semblerait que l’état :hover ne soit pas pris en compte. Les liens fonctionnent bien, et s’ils ont été correctement nommés, le résultat sera une simple image avec des zones cliquables dans Opera 6. On devrait pouvoir vivre avec, surtout depuis que Opera 7 est sorti.

La deuxième remarque sera familière à tout ceux qui ont déjà passé du temps avec la technique FIR. Dans les rares cas où
l’utilisateur aura désactivé les images mais gardé le CSS, un gros blanc va apparaître en lieu et place de notre image. Les liens seront toujours présent et toujours cliquables, mais il n’y aura rien de visuel. Pour l’instant, il n’y a pas de solution pour ce problème.

Ensuite, il y a la taille du fichier. Un fausse idée serait de penser qu’une grande image contenant les états « avant/après » sera plus lourde que plusieurs images découpées, puisque l’image sera plus grande. Mais chaque format d’image contient un entête (ce qui explique qu’un GIF blanc de 1px par 1px fasse environ 50 octets), et plus on découpe les images, plus on a d’entêtes (bah oui, 1 par image). En plus, une grande image générale ne contiendra qu’une seule palette de couleurs pour un GIF, tandis que chaque image découpée aurait la sienne. Les tests préliminaires démontrent un poid de page plus faible avec les Sprites CSS,
ou au pire, un poid supérieur négligeable.

Enfin, n’oublions pas que notre balisage est beau et propre, avec tous les avantages que cela entraine. La liste HTML s’affiche parfaitement dans un environnement dégradé, et une technique de remplacement par l’image laissera le texte des liens accessibles aux navigateurs vocaux. Le remplacement des sprites est très simple, puisque toutes les dimensions et décalages sont gérés par un seul fuchier CSS, et que tous les Sprites sont dans une seule image.

Vous pouvez suivre les réponses à cet article grâce à ce flux RSS 2.0.
Vous pouvez laisser un commentaire, ou faire un trackback.

2 Réponses à “Sprites CSS : La mort du découpage d’image”


  1. guiralantoine :

    Dommage que l’article commence par « une traduction par mes soins….http://www.pompage.net/pompe/sprites/ 3 ans plus tôt :/

  2. Divad :

    Et pourtant, c’est bien ma traduction !! D’ailleurs, en lisant la trad présente sur pompage.net, on voit rapidement que ce ne sont pas les même, et honnêtement, je dois bien avouer que l’autre traduction est meilleure que la mienne ! ^^

Laissez un Commentaire


Vers le haut