[Aller au plan] [Aller au contenu]

Créer un menu à onglets avec CSS

par Normand Lamoureux *

Plan

Introduction

Propos

Je vais montrer comment utiliser le langage des feuilles de style en cascade (CSS) pour créer un menu à onglets. La technique utilisée n'a rien de nouveau, mais elle sera l'occasion d'aborder plusieurs sujets rarement traités en un même endroit. Tout au long de mon exposé, je ferai référence à des considérations méthodologiques, notamment sur les principes de développement, les outils de validation et les raccourcis d'écriture CSS.

Les plus pressés peuvent aller directement au fichier HTML complet, ainsi qu'aux feuilles de style « normal.css », « ie.css » et « ie6.css ».

Explication du projet

Le menu doit ressembler à ce qui suit :

Ce menu doit également répondre aux exigences suivantes :

Bref, le résultat doit être valide, accessible, multi-navigateur et indépendant de CSS, de JavaScript ou d'un outil de pointage particulier. Un beau projet.

Matériel et préparation

Pour écrire une feuille de style, j'utilise le navigateur Firefox avec la barre d'outils Web Developer Toolbar et l'extension ColorZilla. Pour créer mes fichiers, j'utilise mon éditeur HTML préféré. D'autres outils s'ajouteront quand viendra le temps d'effectuer des tests, mais en phase de développement j'ai rarement besoin de plus.

Pour réaliser ce projet, j'aurai besoin des trois images suivantes :

Je m'efforce de donner un nom significatif à mes images, quitte à utiliser plusieurs mots séparés par des traits d'union. Pour qu'il soit plus facile de m'y retrouver, je me sers toujours des mêmes conventions de nommage : jamais de majuscules ni de lettres accentuées ni d'espace. De plus, je regroupe toujours mes images dans un même dossier que je nomme « img ».

Vous pouvez choisir de faire autrement. Ce sont les résultats qui comptent. Mais pour augmenter vos chances de succès, vous y gagnerez à être le plus simple, le plus clair et le plus méthodique possible.

Retourner au plan

Écrire le fichier HTML

Les habitudes de travail

Mon éditeur HTML (HyperText Markup Language) me permet de définir de nombreux raccourcis-clavier. J'en ai pour baliser des paragraphes, des blocs de citation, des cellules de tableau, des éléments de liste, etc. Je les utilise et cela me permet de travailler plus rapidement qu'avec la souris.

Par choix, je travaille directement dans le code et jamais en mode WYSIWYG (What You See Is What You Get). Au début, j'avais adopté cette façon de faire pour apprendre le HTML et m'assurer d'un contrôle total de mon code. Avec le temps, c'est devenu une habitude. Un choix que je n'ai jamais eu à regretter.

Les orientations de fond

Je débute toujours la création d'un nouveau fichier HTML avec le fichier de base qui va suivre. Vous y trouverez des éléments importants qui risquent de passer inaperçus et c'est pourquoi je vais prendre le temps de le commenter.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <meta http-equiv="Content-Language" content="fr">
  <title></title>
  <style type="text/css" media="screen">@import url(css/normal.css);</style>
</head>
<body>
</body>
</html>

Je débute par un DOCTYPE, une ligne de code servant à déclarer la version de HTML qu'on utilise et qu'un développeur sérieux ne doit pas omettre. Comme vous pouvez le constater, je code en XHTML 1.0 et j'encode en utf-8. Un autre encodage pourrait aussi convenir. Par exemple, iso-8859-1 ou iso-8859-15. Qu'on s'en rende compte ou non, il y a toujours un encodage. L'important est de déclarer celui qu'on utilise et de s'assurer que son éditeur de texte le gère correctement. Autrement, des problèmes d'affichage et de validité sont à prévoir.

Je le dis sans détour : le choix entre HTML 4.01 et XHTML 1.0 importe assez peu. Par contre, opter pour un DOCTYPE Strict m'apparaît fondamental et incontournable pour un développement durable. De grâce, ne choisissez Transitional que si vous ne pouvez vraiment pas faire autrement.

Je déclare ensuite la langue principale de mon document avec la valeur fr. Les moteurs de recherche se servent de ce renseignement pour indexer le contenu et le servir ensuite à ceux qui demandent des résultats de recherche dans une langue spécifique. Ceux qui font usage d'une synthèse vocale y trouvent également leur compte. Indexation et accessibilité, deux bonnes raisons de ne pas oublier ce détail.

À moins d'une exception, mon fichier HTML ne contiendra rien d'autre que du HTML. En vertu du principe de séparation entre contenu, présentation et comportement, les règles CSS et les éventuels scripts JavaScript seront appelés depuis des fichiers externes respectivement rangés au préalable dans des dossiers nommés « css » et « js ». Bref, je sais d'avance que mes règles de style seront appelées depuis un fichier contenu dans un dossier « css » situé au même niveau que mes fichiers HTML. D'où la ligne :

<style type="text/css" media="screen">@import url(css/normal.css);</style>

Une feuille de style externe peut aussi être appelée avec link, mais la méthode @import a l'avantage de ne pas être comprise par Netscape Navigator 4, dans lequel une feuille de style le moindrement complexe aurait des résultats catastrophiques. Puisque je ne veux pas m'embêter avec Internet Explorer 4 non plus, j'écris mon adresse URL (Universal Resource Locator) sans l'encadrer de guillemets. Une syntaxe correcte, mais qu'il ne comprendra pas. En conséquence, les navigateurs de génération 4 et moins serviront le fichier HTML sans feuille de style. Ce qui est tout de même mieux que n'importe quoi.

Titrer, baliser, valider

Eu égard à l'importance de l'élément title pour le référencement et l'accessibilité, la toute première chose à faire consiste à doter le fichier d'un titre HTML significatif. Je trouve une formulation qui tient en moins de 10 mots, qui reflète fidèlement le contenu et qui commence par un nom ou un verbe.

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <meta http-equiv="Content-Language" content="fr">
  <title>Créer un menu à onglets avec CSS</title>
  <style type="text/css" media="screen">@import url(css/normal.css);</style>
</head>

Au moment de baliser le contenu, je me concentre sur la structure logique du texte en mettant de côté toute considération esthétique. J'identifie la nature de chaque portion et je choisis les balises les plus appropriées. Je n'utiliserai pas les mêmes, par exemple, selon qu'il s'agit d'un paragraphe, d'un bloc de citation ou d'un titre. Inversement, une paire de balises donnée doit permettre de déduire le type de contenu qui s'y trouve.

Appliquées au cas d'un menu, les considérations qui précèdent me conduisent assez spontanément à regrouper les liens à l'intérieur d'une liste à puces. Car à bien y penser, un menu est effectivement une liste de liens. D'où l'ajout des lignes suivantes à l'intérieur de l'élément body :

<body>
 <ul>
  <li><a href="01.html">Accueil</a></li>
  <li><a href="02.html">Services</a></li>
  <li><a href="03.html">Outils</a></li>
  <li><a href="04.html">En savoir plus</a></li>
 </ul>
</body>

J'indente les lignes pour faciliter la lecture du code. Il faut savoir que les navigateurs ont l'ordre de réduire les espaces multiples à un seul. Ce qui permet d'utiliser espacements, tabulations et retours de chariot pour disposer le code comme on le souhaite, sans conséquence sur l'affichage final. Puisque tous les navigateurs ont un comportement identique sur ce point, aussi bien en profiter!

Remarquez qu'à ce stade de développement, je n'ai encore ni div, ni span, ni class, ni id. Ces éléments n'ayant pas de valeur sémantique propre, je ne les ajouterai qu'en second lieu, lorsque viendra le temps d'envisager le rendu visuel. Il est d'ailleurs à noter que les débutants ont tendance à abuser des div et des span. Une maladie qu'on pourrait nommer la « spandivite ».

Autre bonne méthode : je prends soin de valider mon fichier HTML avant de commencer à rédiger ma feuille de style. À cet effet, je l'affiche dans Firefox et je me sers de l'élément « Valider le HTML local » du menu Outils de la barre d'outils pour développeur Web. Pour ceux qui aiment les raccourcis-clavier, un petit coup de Contrôle-Majuscule-A et le tour est joué.

Retourner au plan

1re phase d'écriture CSS

Arrive le moment d'écrire ma feuille de style. Dans ce dessein, je vais à l'élément « Éditer les CSS » du menu CSS de la barre d'outils pour développeur Web. Cette fonctionnalité permet d'écrire une règle CSS dans le volet de gauche du navigateur et de voir immédiatement le résultat dans le volet de droite, où le fichier HTML s'affiche.

Dresser la liste des sélecteurs

La première chose consiste à dresser la liste des sélecteurs CSS. Je fais cette énumération en suivant simplement l'ordre de lecture du code HTML. Cette façon de procéder permet de reproduire l'arbre de mon document et d'assurer ainsi une meilleure transition entre HTML et CSS. Une transition que les débutants trouvent souvent difficile à exécuter.

body {}
ul {}
li {}
a {}

Paramétrer l'élément body

Cela fait, je peux commencer à renseigner mes blocs de déclarations. Je débute toujours par l'élément body, dans lequel j'essaie d'inclure tous les styles qui seront transmis par héritage aux éléments subséquents; nommément la famille et la taille de police, la hauteur de ligne, la couleur du texte et la couleur d'arrière-plan.

body {
  margin: 0px;
  padding: 0px;
  font-size: 100%;
  line-height: 1.6;
  font-family: tahoma,arial,helvetica,"bitstream vera sans",sans-serif;
  color: #000000;
  background-color: #ffffff;
}

Je mets les marges et les remplissages (padding) à 0px pour niveler les différences de comportement des navigateurs dans leur manière d'afficher l'élément body. Ensuite, je fixe la taille de ma police à 100%. Cette valeur correspond à la taille par défaut définie dans le navigateur de l'utilisateur. À moins que ce dernier ne l'ait changée, cette taille est de 16 pixels; ce qui correspond à 12 points. Notez qu'une taille de police définie en pixels ne pourra pas être redimensionnée dans Internet Explorer. Pour pallier cet inconvénient, je définis toujours mes tailles de police avec des unités de mesure élastiques, comme % ou em. Je définis ensuite ma hauteur de ligne. La valeur par défaut est à environ 1.2. Je l'augmente à 1.6 pour gagner en lisibilité.

Arrive l'énumération des familles de polices. Le navigateur cherchera à établir une correspondance entre cette liste et les polices qui se trouvent installées dans le système d'exploitation de l'utilisateur, puis il se servira de la première qu'il trouve. J'énumère donc mes polices par ordre de préférence, et je termine par une famille de police générique : sans-serif. Notez que le nom d'une police doit être mis entre guillemets lorsqu'il contient un ou plusieurs espaces.

Enfin, je termine toujours en définissant une couleur de police et une couleur d'arrière-plan explicites pour éviter les mauvaises surprises dans les navigateurs qui auraient des valeurs par défaut différentes des valeurs communes. Je définis ces valeurs en m'assurant de conserver un contraste fort pour des raisons évidentes d'accessibilité. À moins d'indications contraires, je mets noir sur blanc, le contraste de lecture auquel tout le monde est habitué.

Paramétrer les éléments ul et li

Je traite l'élément ul avant les éléments li et a par souci de méthodologie. En effet, dans le maniement des CSS, il vaut généralement mieux procéder du général au particulier.

ul {
  float: right;
  margin-top: 16px;
  margin-right: 16px;
  margin-bottom: 0px;
  margin-left: 0px;
  padding: 0px;
  list-style: none;
}

Je me sers de float: right pour repousser le menu à droite. Remarquez que c'est à l'élément ul que je dois attribuer cette déclaration. L'attribuer à l'élément li aurait l'inconvénient de renverser l'ordre entre les onglets. Ensuite, je définis des valeurs explicites pour les marges et le remplissage afin de contourner les divergences de comportement entre les différents navigateurs dans l'affichage des listes à puces. Pour laisser respirer mon menu, je le décolle du coin supérieur droit en laissant une marge supérieure et une marge droite de 16 pixels chaque. Enfin, je fais disparaître les puces avec list-style: none.

li {
  float: left;
  margin-left: 3px;
}

Il me faut disposer les éléments li à l'horizontale. Ce que je réussis à obtenir en leur affectant un float: left. C'est à l'élément li qu'il convient d'affecter un style pour régler les espaces entre les onglets. Puisque mon menu doit rester à 16 pixels du bord droit, je ne peux pas me servir de margin-right. D'où le margin-left: 3px.

Notez que pour le moment j'écris mes règles de styles sans me soucier des raccourcis d'écriture CSS. Je fais même exprès d'étirer mon code au maximum. Le contraste avec le code condensé n'en sera que plus frappant lorsqu'on appliquera plus tard les raccourcis d'écriture.

Retourner au plan

Retouches au fichier HTML

Ajouter des span enfants de a

La prochaine étape consiste à créer les coins supérieurs arrondis des onglets. Cet effet sera obtenu en ayant recours à des images d'arrière-plan. La largeur des onglets n'étant pas toujours la même, et les images ayant la propriété d'avoir des tailles rigides, il est clair qu'il ne sera pas possible de créer les deux coins arrondis d'un coup avec une seule image. J'aurai donc recours à deux images : l'une pour le coin supérieur gauche, et l'autre pour le coin supérieur droit.

L'une des images aurait pu être en arrière-plan de l'élément a, et l'autre, en arrière-plan de l'élément li. Le premier de ces éléments étant enfant du second, il aurait suffit d'affecter les styles voulus à li:hover pour faire réagir les deux images en même temps au survol de l'élément li par le pointeur de souris. Sauf que les pseudo-classes dynamiques ne fonctionnent que sur un élément a dans Internet Explorer.

<body>
 <ul>
  <li><a href="01.html"><span>Accueil</span></a></li>
  <li><a href="02.html"><span>Services</span></a></li>
  <li><a href="03.html"><span>Outils</span></a></li>
  <li><a href="04.html"><span>En savoir plus</span></a></li>
 </ul>
</body>

La solution à ce problème consiste à ajouter des span. De cette façon, une des deux images sera en arrière-plan de l'élément a, et l'autre, en arrière-plan de l'élément span. Ce qui rendra possible de les faire réagir depuis l'élément a.

Ajouter un conteneur div

Un élément positionné avec float occupera autant d'espace vertical que nécessaire pour envelopper les éléments qu'il contient, mais pas plus. Et ce, quand bien même on lui ajouterait des marges et des espacements. Par ailleurs, notre projet initial veut que le menu se superpose à un arrière-plan qui traverse toute la largeur de fenêtre du navigateur. Il en découle que nous ne pourrons pas paramétrer cet arrière-plan à même l'élément ul. En revanche, nous serions tirés d'embarras si l'élément ul avait un élément parent auquel nous pourrions affecter tous les styles voulus.

Dans l'état actuel de mon code HTML, l'élément ul est dépourvu d'élément parent que j'aurais le loisir de paramétrer librement. Je vais donc retourner à mon fichier HTML pour ajouter un div qui servira à cette fin.

<body>
<div id="en-tete">
  <ul>
    <li><a href="01.html"><span>Accueil</span></a></li>
    <li><a href="02.html"><span>Services</span></a></li>
    <li><a href="03.html"><span>Outils</span></a></li>
    <li><a href="04.html"><span>En savoir plus</span></a></li>
  </ul>
</div>
</body>

Puisque l'élément div est sémantiquement indéterminé, je lui ai accolé un nom qui permet à la fois de comprendre son rôle dans le code et de le distinguer des éventuels conteneurs div qui pourraient s'ajouter dans le cadre d'un projet plus vaste. Comme je n'aurai qu'un seul en-tête par fichier, je me sers d'un id plutôt que d'une class, deux attributs qui, au passage, s'opposent comme le nom propre au nom commun.

Valider le fichier

Étant méthodologique, je prends soin de valider de nouveau mon fichier HTML avant de poursuivre l'écriture de ma feuille de style. Le code ajouté n'est pas complexe, j'en conviens, mais nul n'est à l'abri d'une faute d'inattention. Puisque les CSS reposent sur le HTML, c'est la seule façon de construire sur des bases solides.

Retourner au plan

2e phase d'écriture CSS

Mettre la liste des sélecteurs à jour

Les modifications apportées au fichier HTML doivent se refléter dans ma feuille de style.

body {}
div {}
ul {}
li {}
a {}
span {}

Les sélecteurs div et span sont ajoutés à la liste. Je les ai disposés de façon à reproduire l'ordre dans lequel les éléments auxquels ils correspondent se présentent dans le code HTML. Mais pour que la feuille de style soit encore plus facile à comprendre et à gérer, je fais précéder chaque sélecteur d'élément par le sélecteur d'identifiant unique dont il est enfant, s'il y en a un, puis j'ajoute le ou les éléments qui l'en séparent.

body {}
#en-tete {}
#en-tete ul {}
#en-tete ul li {}
#en-tete ul li a {}
#en-tete ul li a span {}

On peut organiser son code CSS de bien d'autres manières. Mais celle que je vous présente ici a le mérite, d'une part, de faire ressortir le lien entre les sélecteurs CSS et les éléments HTML et, d'autre part, de donner le contexte dans lequel s'inscrit l'élément HTML visé par le sélecteur CSS.

Paramétrer l'en-tête

Mon nouveau conteneur en place, il reste à lui appliquer les styles voulus. Mon fichier HTML n'étant plus tout à fait le même, il me faut rafraîchir la fenêtre de mon navigateur avant de procéder. Un petit coup sur la touche de fonction F5 et le tour est joué.

Comme aucune largeur ne lui est explicitement affectée, mon #en-tete occupera tout l'espace horizontal disponible et c'est ce que je veux. Nous verrons plus loin qu'Internet Explorer n'entend pas les choses ainsi, mais nous règlerons ce problème en temps et lieu. Pour le moment, je m'en tiens à la manière dont les choses doivent se passer dans un navigateur conforme à la norme CSS 2.

#en-tete {
  overflow: hidden;
  background-color: #dae0d2;
  background-image: url(../img/fond.png);
  background-position: bottom;
  background-repeat: repeat-x;
}

Verticalement, mon #en-tete doit avoir exactement la même hauteur que son contenu. Le problème, c'est que ce contenu est en flottement et que ce qui flotte n'a aucune influence sur la taille du conteneur. Pour obtenir l'espacement vertical voulu, il faut empêcher l'élément div de laisser son contenu déborder. Ce qu'on obtient avec overflow: hidden.

Reste à traiter l'image d'arrière-plan, que j'appelle avec background-image suivi de l'URL appropriée. Le bas de cette image doit toujours rester collé à la limite inférieure du menu. D'où le background-position: bottom. Comme je veux que mon image se répète à l'horizontal seulement, je mets background-repeat: repeat-x. Enfin, je prélève la couleur du bord supérieur de mon image avec la pipette de ColorZilla, et je l'affecte comme couleur d'arrière-plan avec background-color: #dae0d2. Cette mesure prévient l'apparition d'un fond blanc au moment d'agrandir la taille d'affichage des polices.

Faire apparaître les coins

J'aurais pu faire l'inverse, mais j'ai préféré me servir de l'arrière-plan de l'élément a pour afficher mon image de coin gauche, et de l'arrière-plan de l'élément span pour afficher mon image de coin droit. Pour être sûr de faire coïncider les éléments a et span en hauteur, j'affecte aux deux un float: left.

#en-tete ul li a {
  float: left;
  padding-left: 10px;
  background-image: url(../img/coin-gauche.png);
  background-repeat: no-repeat;
}
#en-tete ul li a span {
  float: left;
  padding-right: 10px;
  background-image: url(../img/coin-droit.png);
  background-repeat: no-repeat;
}

Contrairement à margin, la propriété padding laisse transparaître l'arrière-plan. C'est donc elle qu'il me faut utiliser pour afficher mes coins supérieurs arrondis. Puisque le texte doit demeurer horizontalement centré à l'intérieur des onglets, le padding-left de l'élément a et le padding-right de l'élément span doivent avoir la même valeur. D'où le 10px affecté à chacun.

Pour que mes fameux coins s'affichent, j'appelle les images avec la propriété background-image suivies de l'URL appropriée à chacune. À moins d'indications contraires, les navigateurs vont répéter ces images à l'horizontale et à la verticale. Pour empêcher ce comportement et bloquer toute répétition, j'enchaîne avec background-repeat: no-repeat.

#en-tete ul li a {
  float: left;
  text-decoration: none;
  letter-spacing: 1px;
  padding-left: 10px;
  background-image: url(../img/coin-gauche.png);
  background-repeat: no-repeat;
}
#en-tete ul li a span {
  float: left;
  padding-right: 10px;
  padding-top: 4px;
  padding-bottom: 3px;
  padding-left: 0px;
  background-image: url(../img/coin-droit.png);
  background-repeat: no-repeat;
}

Je fais disparaître le soulignement des liens avec text-decoration: none, et j'en profite pour aérer mon texte en ajoutant un peu d'espace entre les lettres avec letter-spacing: 1px.

Comme c'est l'élément span qui se trouve le plus près du texte, c'est à lui qu'il convient d'attribuer les espacements souhaités pour faire respirer le texte à l'intérieur des onglets. Il ne faut pas perdre de vue que c'est la propriété padding qu'il faut utiliser et non la propriété margin. Remarquez que je m'arrange pour regrouper les valeurs de padding. Le moment venu, les retouches au code n'en seront que plus faciles à faire.

#en-tete ul li a {
  float: left;
  text-decoration: none;
  letter-spacing: 1px;
  padding-left: 10px;
  background-image: url(../img/coin-gauche.png);
  background-repeat: no-repeat;
  background-position: 0% -250px;
}
#en-tete ul li a span {
  float: left;
  padding-right: 10px;
  padding-top: 6px;
  padding-bottom: 3px;
  padding-left: 0px;
  background-image: url(../img/coin-droit.png);
  background-repeat: no-repeat;
  background-position: 100% -250px;
  color: #ffffff;
}

Reste à positionner les images d'arrière-plan pour obtenir la couleur d'onglet voulue. Ce que je fais avec la propriété background-position. Étant donné la façon dont mes images ont été construites, il faut les déplacer toutes deux de 250 pixels vers le haut. Ce que je fais avec la valeur -250px. Il faut également s'arranger pour faire coïncider le bord gauche de l'image du coin gauche avec le bord gauche de l'élément a, et le bord droit de l'image du coin droit avec le bord droit de l'élément span. D'où les valeurs 0% et 100%.

Il faut savoir que la première valeur attribuée à la propriété background-position règle le positionnement horizontal de l'image, tandis que la seconde règle son positionnement vertical. Remarquez qu'il n'y a aucun inconvénient à ce qu'une de ces valeurs soit exprimée en pourcentage et l'autre en pixel. Enfin, je mets la couleur de mon texte en blanc avec color: #ffffff.

Retourner au plan

3e phase d'écriture CSS

Paramétrer l'onglet actif

L'une des conditions du projet initial veut que l'onglet de la page en cours ne soit pas cliquable. Pour y arriver, il n'y a rien d'autre à faire que de retrancher l'élément a qui entoure le texte de l'onglet.

<body>
<div id="en-tete">
  <ul>
    <li id="actif"><span>Accueil</span></li>
    <li><a href="02.html"><span>Services</span></a></li>
    <li><a href="03.html"><span>Outils</span></a></li>
    <li><a href="04.html"><span>En savoir plus</span></a></li>
  </ul>
</div>
</body>

Le coin droit continuera de s'afficher en arrière-plan de l'élément span, mais il faudra désormais compter sur l'élément li pour le coin gauche. Ce qui ne posera pas problème, puisque l'onglet actif n'a pas besoin de réagir au survol du pointeur de la souris. Reste qu'il faudra distinguer ce li des autres et c'est exactement ce à quoi sert l'identifiant unique id="actif".

Mon code HTML ayant changé, je le valide avec Contrôle-Majuscule-A, et je rafraîchis la fenêtre de mon navigateur avec F5 avant de retourner à l'écriture de ma feuille de style.

#en-tete ul li#actif {
  float: left;
  text-decoration: none;
  letter-spacing: 1px;
  padding-left: 10px;
  background-image: url(../img/coin-gauche.png);
  background-repeat: no-repeat;
  background-position: 0% 0%;
}
#en-tete ul li#actif span {
  float: left;
  padding-right: 10px;
  padding-top: 6px;
  padding-bottom: 3px;
  padding-left: 0px;
  background-image: url(../img/coin-droit.png);
  background-repeat: no-repeat;
  background-position: 100% 0%;
  color: #333333;
}

L'onglet actif doit recevoir les mêmes valeurs et propriétés CSS que les autres onglets, à l'exception des couleurs d'avant-plan et d'arrière-plan. Je fais donc un copier-coller des règles déjà existantes et je remplace li a par le sélecteur li#actif partout. Après quoi, il ne me reste plus qu'à mettre la valeur du positionnement vertical de mes images d'arrière-plan à 0% pour faire apparaître mes coins en blanc, puis à faire passer la couleur de mon texte de blanc à gris foncé avec #333333.

Rendre les onglets réactifs

Pour que la couleur d'un onglet change, il faut que les images d'arrière-plan des éléments a et span changent en même temps. Pour que ce changement se produise au survol du pointeur de la souris, il faut utiliser la pseudo-classe dynamique :hover. Et pour que l'effet se produise aussi dans Internet Explorer, comme je l'ai déjà dit, il faut affecter :hover à l'élément a.

#en-tete ul li a:hover {
  background-position: 0% -500px;
}
#en-tete ul li a:hover span {
  background-position: 100% -500px;
}

Au survol de l'élément a par le pointeur de la souris, la position de l'image d'arrière-plan de l'élément a sera décalée de 500 pixels vers le haut. Ce qui permettra de positionner l'image à la couleur d'onglet souhaitée. J'obtiens ce résultat grâce au -500px affecté à la propriété background-position. Au même moment, grâce à l'autre règle et au sélecteur #en-tete ul li a:hover span, l'image d'arrière-plan de l'élément span subira le même sort.

Selon les termes du projet initial, la réactivité des onglets doit aussi se produire au clavier. Pour y arriver, il suffit d'utiliser la pseudo-classe dynamique :focus.

#en-tete ul li a:hover {
  background-position: 0% -500px;
}
#en-tete ul li a:focus {
  background-position: 0% -500px;
}
#en-tete ul li a:hover span {
  background-position: 100% -500px;
}
#en-tete ul li a:focus span {
  background-position: 100% -500px;
}

Je fais un copier-coller de ma première règle juste en-dessous de cette dernière et je change simplement :hover pour :focus. Je répète exactement cette routine pour la seconde règle et le tour est joué. Il y a bien sûr moyen de diminuer le nombre de lignes de code et d'économiser un peu d'espace, mais pour le moment, je ne me soucie pas de ce genre de détail.

Valider le code CSS

L'écriture de mon code CSS terminée, il ne me reste plus qu'à en faire la validation. Je sélectionne l'ensemble de mon code avec Contrôle-A, j'ouvre un nouvel onglet dans mon navigateur avec Contrôle-T, je me rends sur le site du Validateur CSS du W3C, je choisis l'onglet « Par saisie directe », je colle le contenu de mon presse-papiers dans la zone de saisie avec Contrôle-V et j'active le bouton « Vérifier ».

Une fois que mon code est valide, je crée dans mon dossier « css » un fichier texte que je nomme « normal.css ». J'ouvre ce fichier avec Entrée, j'y colle le contenu de mon presse-papiers avec Contrôle-V, puis je le sauvegarde avec un petit coup de Contrôle-S. Après quoi, n'ayant plus besoin du volet de gauche de mon navigateur pour écrire les CSS, je le referme.

Retourner au plan

Supporter Internet Explorer

Importance, enjeu et méthode

Il est hors de question de développer une page Web pour un navigateur particulier à l'exclusion des autres. Fût-ce le navigateur le plus en vogue. Il faut assurer l'universalité d'accès aux contenus, la robustesse du code et la pérennité des données. L'avenir du Web en dépend.

Inversement, il serait irresponsable de développer un site conforme qui ne fonctionnerait pas bien pour une masse importante d'utilisateurs, sous prétexte que c'est leur navigateur qui est fautif. Car au bout du compte, le Web, les normes, les pages et tout le reste, c'est pour les utilisateurs. Il faut donc trouver un moyen de supporter Internet Explorer sans sacrifier l'idéal à atteindre.

Ce moyen existe et il consiste à contourner les caprices d'Internet Explorer en utilisant un « commentaire conditionnel » pour appeler une feuille de style qui lui est spécifiquement dédiée. Depuis une simple ligne de code dans mon fichier HTML, il est en effet possible d'appeler une feuille de style qui sera prise en considération par Internet Explorer et ignorée de tous les autres navigateurs.

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <meta http-equiv="Content-Language" content="fr">
  <title>Créer un menu à onglets avec CSS</title>
  <style type="text/css" media="screen">@import url(css/normal.css;)</style>
  <!--[if IE]><style type="text/css" media="screen">@import url(css/ie.css);</style><![endif]-->
</head>

Observez la syntaxe particulière de cette ligne de code. Elle débute par <!-- et se termine par -->, les signes distinctifs d'un commentaire HTML ordinaire. Ce qui en garantit déjà la validité. Le if et le endif mis entre crochets se répondent, et IE vaut pour « Internet Explorer ». Entre les expressions par lesquelles débute et se termine le commentaire conditionnel proprement dit, se trouve une ligne de code servant à appeler une feuille de style dédiée à Internet Explorer, et que j'ai nommée pour cette raison « ie.css ».

Concrètement, les choses vont se passer comme suit : grâce à @import, une feuille de style conforme à la norme CSS 2 sera appelée et prise en charge par tous les navigateurs qui le peuvent; ensuite, une feuille de style dédiée à Internet Explorer sera appelée et prise en charge uniquement par les navigateurs concernés, grâce à la technologie des commentaires conditionnels, seulement comprise des navigateurs de Microsoft. Si des styles de même poids entrent en conflit d'une feuille à l'autre, ceux appelés en second écraseront les premiers.

Les utilisateurs d'un navigateur conforme y trouveront leur compte, les utlisateurs d'Internet Explorer aussi et mes feuilles CSS seront exemptes de ces astuces plus ou moins douteuses que sont les hacks. Car il faut bien se rendre à l'évidence : les hacks reposent toujours sur une syntaxe hétérodoxe ou un bogue. Donc sur quelque chose qui ne va pas et qui risque de ne plus fonctionner à plus ou moins brève échéance. Les commentaires conditionnels, au contraire, font appel à une fonctionnalité expressément prévue par les développeurs d'Internet Explorer. Voilà pourquoi on ne risque rien à les utiliser.

Dresser la liste des problèmes

Au moment d'écrire ces lignes, le navigateur Opera offre un meilleur support de la norme CSS 2 que ne le fait Firefox sur certains points. Aussi, la première chose à faire est de s'assurer qu'il n'y a pas de divergence d'interprétation entre Opera et Firerox. J'ai lancé Opera, j'y ai ouvert ma page et tout fonctionne exactement comme prévu. Cela fait, je peux passer aux tests sur Internet Explorer avec la relative certitude que ce sera lui le fautif si quelque chose n'allait pas. Le support des CSS ayant été amélioré dans IE 7, je commence par IE 6, où il risque d'y avoir plus de problèmes.

J'affiche le résultat de mon travail dans IE 6, je mets tout ce que je peux à l'épreuve et j'observe attentivement. Quatre problèmes se manifestent. D'abord, le menu s'affiche comme s'il n'y avait pas du tout d'en-tête. Ensuite, au lieu d'être à 16 pixels du bord droit de la zone d'affichage du navigateur, mon menu se trouve à 32 pixels. Troisièmement, au survol des onglets, le pointeur de la souris ne se transforme pas en une main comme il devrait. Enfin, les onglets ne changent pas de couleur au moment de recevoir l'attention lorsque la navigation se fait au clavier.

Viennent les tests dans IE 7, où seuls les deux derniers problèmes se manifestent. La conclusion s'impose : il faudra deux feuilles de style distinctes. L'une pour corriger les problèmes communs à toutes les versions d'IE; l'autre pour corriger les problèmes propres aux versions égale ou inférieures à IE 6. Ce qui me conduit à ajouter la ligne de code que voici, juste sous l'autre commentaire conditionnel :

<!--[if lte IE 6]><style type="text/css" media="screen">@import url(css/ie6.css);</style><![endif]-->

L'expression lte IE 6 vaut pour less than or equal to Internet Explorer 6. Le fait de pouvoir viser des versions spécifiques d'Internet Explorer est un autre avantage important des commentaires conditionnels.

Je viens de procéder à la dernière retouche au fichier HTML. Ces retouches ont été suffisamment nombreuses pour qu'il vaille la peine de montrer à quoi ressemble la totalité de ce fichier. Voici :

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <meta http-equiv="Content-Language" content="fr">
  <title></title>
  <style type="text/css" media="screen">@import url(css/normal.css);</style>
  <!--[if IE]><style type="text/css" media="screen">@import url(css/ie.css);</style><![endif]-->
  <!--[if lte IE 6]><style type="text/css" media="screen">@import url(css/ie6.css);</style><![endif]-->
</head>
<body>
  <div id="en-tete">
    <ul>
      <li><a href="01.html"><span>Accueil</span></a></li>
      <li><a href="02.html"><span>Services</span></a></li>
      <li><a href="03.html"><span>Outils</span></a></li>
      <li><a href="04.html"><span>En savoir plus</span></a></li>
    </ul>
  </div>
</body>
</html>

Mettre les solutions en place

Il faut maintenant réfléchir aux problèmes soulevés. Le premier vient de ce que je n'ai pas affecté de taille explicite au conteneur div chargé d'afficher l'arrière-plan. Les navigateurs conformes comprennent qu'un conteneur doit envelopper tous les éléments qu'il contient, même lorsque ces derniers sont en flottement, ce qui n'est pas le cas des versions d'Internet Explorer inférieures à 7. Ces dernières ont besoin d'une valeur explicite en largeur ou en hauteur. À défaut de quoi elles afficheront le contenu, mais pas le conteneur. Le plus surprenant est que n'importe quelle valeur exprimée dans une unité de mesure relative suffira, même une valeur apparemment trop petite. Mettre height: 1% réglera donc le problème.

Le deuxième problème provient d'un autre bogue connu d'Internet Explorer; bogue qui lui fait doubler la valeur de la marge d'un élément flottant lorsque la marge se trouve du même côté que le flottement. Ce qui est bien le cas ici, puisque j'ai effectivement ajouté une marge droite à un élément positionné avec float: right.

#en-tete {
  height: 1%;
}
#en-tete ul {
  position: relative;
  right: -16px;
}

Puisqu'elles ne concernent que les versions inférieures ou égale à 6 d'Internet Explorer, ces règles seront inscrites dans un fichier que j'ai appelé pour cette raison « ie6.css ». Le positionnement relatif permet de repositionner un élément à partir de l'endroit où il a d'abord été positionné par le navigateur. Le positionnement opéré par les versions d'IE inférieures à 7 étant fautif, il suffit de l'accepter d'abord et d'y apporter la correction voulue tout de suite après. D'où le right: -16px.

Le troisième problème vient de l'utilisation du sélecteur de pseudo-classe dynamique :hover sans avoir défini de valeur explicite à la propriété CSS cursor. Tous les navigateurs comprendront que cette valeur n'a pas changé, sauf IE. Il faudra mettre cursor: pointer.

Le quatrième problème est plus complexe. Pour faire réagir mes onglets au clavier, je me suis servi de la pseudo-classe dynamique :focus, expressément prévue pour cela. Le problème vient de ce qu'Internet Explorer ne la supporte pas. En revanche, il interprète la pseudo-classe :active comme les navigateurs conformes interprètent :hover. Fort heureusement, il n'y a aucun inconvénient à ce que ces pseudo-classes soient simultanément affectées au même élément. Pour obtenir l'effet de clavier voulu dans IE, réécrire les mêmes règles avec :active à la place de :hover devrait donc suffire.

a:hover {
  cursor: pointer;
}
#en-tete ul li a:active {
  background-position: 0% -500px;
}
#en-tete ul li a:active span {
  background-position: 100% -500px;
}

Puisque ces règles permettent de résoudre des problèmes communs à toutes les versions d'Internet Explorer, elles seront inscrites dans un fichier que j'ai appelé pour cette raison « ie.css ».

Retourner au plan

Compacter le code CSS

Aperçu des fichiers de départ

Arrive le moment de parler des raccourcis d'écriture prévus par la norme CSS 2. Une fois tous les morceaux de code rassemblés et mis les uns à la suite des autres, mon fichier CSS principal se compose des 80 lignes que voici :

body {
  margin: 0px;
  padding: 0px;
  font-size: 100%;
  font-family: tahoma,arial,helvetica,"bitstream vera sans",sans-serif;
  color: #000000;
  background-color: #ffffff;
}
#en-tete {
  overflow: hidden;
  background-color: #dae0d2;
  background-image: url(../img/fond.png);
  background-position: bottom;
  background-repeat: repeat-x;
}
#en-tete ul {
  float: right;
  margin-top: 16px;
  margin-right: 16px;
  margin-bottom: 0px;
  margin-left: 0px;
  padding: 0px;
  list-style: none;
}
#en-tete ul li {
  float: left;
  margin-left: 3px;
}
#en-tete ul li a {
  float: left;
  text-decoration: none;
  letter-spacing: 1px;
  padding-left: 10px;
  background-image: url(../img/coin-gauche.png);
  background-position: 0% -250px;
  background-repeat: no-repeat;
}
#en-tete ul li#actif {
  float: left;
  text-decoration: none;
  letter-spacing: 1px;
  padding-left: 10px;
  background-image: url(../img/coin-gauche.png);
  background-position: 0% 0%;
  background-repeat: no-repeat;
}
#en-tete ul li a span {
  float: left;
  padding-top: 6px;
  padding-right: 10px;
  padding-bottom: 3px;
  padding-left: 0px;
  background-image: url(../img/coin-droit.png);
  background-position: 100% -250px;
  background-repeat: no-repeat;
  color: #ffffff;
}
#en-tete ul li#actif span {
  float: left;
  padding-top: 6px;
  padding-right: 10px;
  padding-bottom: 3px;
  padding-left: 0px;
  background-image: url(../img/coin-droit.png);
  background-position: 100% 0%;
  background-repeat: no-repeat;
  color: #333333;
}
#en-tete ul li a:hover {
  background-position: 0% -500px;
}
#en-tete ul li a:focus {
  background-position: 0% -500px;
}
#en-tete ul li a:hover span {
  background-position: 100% -500px;
}
#en-tete ul li a:focus span {
  background-position: 100% -500px;
}

Mon fichier « ie.css » devrait ressembler à ceci :

a:hover {
  cursor: pointer;
}
#en-tete ul li a:active {
  background-position: 0% -500px;
}
#en-tete ul li a:active span {
  background-position: 100% -500px;
}

Et le fichier « ie6.css », enfin, à ceci :

#en-tete {
  width: 100%;
}
#en-tete ul {
  position: relative;
  right: -16px;
}

Les étapes à suivre

Avant de simplifier mon code, je m'assure de sa validité en utilisant la méthode signalée plus haut. Cela fait, je lui fais subir une cure d'amaigrissement en 10 étapes.

Première étape : éliminer les %, les px et autres symboles d'unité accolés aux valeurs 0.

Deuxième étape : compacter les valeurs de marge et de remplissage en utilisant le truc de l'horloge. Par exemple, au lieu d'écrire margin-top: 16px suivi de margin-right: 16px suivi de margin-bottom: 0 suivi de margin-left: 0, il suffit de mettre margin: 16px 16px 0 0 pour obtenir exactement le même résultat. Les navigateurs distribueront les valeurs suivant l'ordre haut, droite, bas, gauche. D'où l'idée de l'horloge. Si on ne met que 2 valeurs au lieu de 4, la première vaudra pour haut et bas, la seconde pour gauche et droite. Si on en met 3, la première vaudra pour haut, la deuxième pour gauche et droite, la troisième pour bas. Enfin, si on n'en met qu'une, elle sera attribuée aux 4 côtés.

La troisième étape concerne les couleurs. Lorsque les valeurs de rouge, de vert et de bleu d'une couleur exprimée au format #rrvvbb forment une suite de 3 doublets, écrire la moitié de chaque doublet suffit. Par exemple, color: #003377 peut se réduire à color: #037.

La quatrième étape consiste à regrouper les règles fondées sur la propriété background. On écrit alors, dans l'ordre, les valeurs se rapportant aux propriétés background-attachment, background-color, background-image, background-position et background-repeat.

La cinquième étape a trait aux polices de caractères. Lorsqu'on a au moins font-size et font-family dans le même bloc de déclarations, il est possible de réduire toutes les règles basées sur font à une seule. Il suffit d'énumérer les valeurs se rapportant aux propriétés font-style, font-weight, font-variant, font-size, line-height et font-family en suivant le schéma font: italic bold small-caps 100%/1.6 arial, sans-serif.

Sixième étape : simplifier les sélecteurs en tenant compte de leurs poids respectifs. Les sélecteurs d'identifiant unique, comme #actif, sont ceux qui ont le plus de poids. Viennent ensuite les sélecteurs de classe du genre .exemple, puis les sélecteurs d'élément comme ul. Ici, il faut s'assurer que ce qu'on enlève n'aura pas de conséquences fâcheuses en cas de conflit entre plusieurs règles.

Septième étape : regrouper les sélecteurs auxquels se rapportent les mêmes styles. Par exemple, au lieu d'avoir a {…} et span {…} avec des déclarations identiques de part et d'autre, attribuer le même bloc de déclarations aux deux sélecteurs après les avoir rassemblés comme ceci : a, span {…}.

La huitième étape vise à supprimer les caractères inutiles. Le point-virgule est nécessaire pour séparer chaque déclaration. On peut donc éliminer les derniers de chaque bloc, puisqu'ils ne séparent rien du tout. Les espaces après une virgule ne sont pas requis. Avant et après un deux-points non plus. S'il y en a, on peut les retrancher. Enfin, le code serait aussi clair si l'accolade fermante se trouvait à la fin de la dernière déclaration, plutôt qu'isolée sur la ligne suivante. Ce qui nous fait gagner quelques retours de chariot.

La neuvième étape consiste à réunir toutes les déclarations d'un même bloc sur une même ligne, au lieu de les laisser sur des lignes séparées. Au début, le code sera plus difficile à consulter, mais on s'y habitue très rapidement.

Dixième et dernière étape : revalider le code de chaque fichier pour être certain de ne pas avoir commis d'erreur de manipulation ou de frappe.

Présentation des résultats

L'application des raccourcis d'écriture dont je viens de parler devrait permettre de passer d'un fichier CSS de 80 lignes à un fichier de 10 lignes seulement. Au même moment, le nombre de caractères passera de 1 852 à 855. Une réduction de 54 %. Ce qui est plus que significatif.

body {margin:0; padding:0; font:100% tahoma,arial,helvetica,"bitstream vera sans",sans-serif; color:#000; background:#fff}
#en-tete {overflow:hidden; background:#dae0d2 url(../img/fond.png) bottom repeat-x}
#en-tete ul {float:right; margin:16px 16px 0 0; padding:0; list-style:none}
#en-tete li {float:left; margin-left: 3px}
#en-tete a, #actif {float:left; text-decoration:none; letter-spacing:1px; padding-left:10px; background:url(../img/coin-gauche.png) 0% -250px no-repeat}
#en-tete span, #actif span {float:left; padding:6px 10px 3px 0; background:url(../img/coin-droit.png) 100% -250px no-repeat; color:#fff}
#actif {background-position:0 0}
#actif span {background-position:100% 0; color:#333}
#en-tete a:hover, #en-tete a:focus {background-position:0 -500px}
#en-tete a:hover span, #en-tete a:focus span {background-position:100% -500px}

Je n'en ai pas soufflé mot jusqu'ici, mais j'ai l'habitude de toujours écrire mes propriétés CSS dans le même ordre. Par exemple, les éventuels display, clear et float au début, suivi des margin, padding, width et height. Au milieu, je mets ce qui se rapporte à font et à line-height. Et, à la fin, arrivent color et background. Cette façon de faire n'a rien d'obligatoire, mais elle facilite la lecture d'un code compact.

Voici maintenant à quoi ressemble le fichier « ie.css » compacté :

#en-tete {height:1%}
#en-tete ul {position:relative; right:-16px}

Et le fichier « ie6.css » compacté, enfin :

a:hover {cursor:pointer}
#en-tete a:active {background-position:0 -500px}
#en-tete a:active span {background-position:100% -500px}

Normand Lamoureux
1er janvier 2007

© Normand Lamoureux, 2007. Tous droits réservés. Courriel.

Expert en accessibilité du Web, Normand Lamoureux est spécialiste en réadaptation de la déficience visuelle à l'Institut Nazareth et Louis-Braille. Il est membre fondateur de la Coopérative de solidarité pour l'accessibilité numérique – plus connue sous le nom d'AccessibilitéWeb – et vice-président du W3Québec.

Révision : Catherine Saguès.

Haut de page