La plupart des sites et applications web modernes contiennent un menu. Construire un tel menu a longtemps impliqué l'utilisation intensive de javascript, mais cela est maintenant possible en utilisant uniquement les propriétés avancées des feuilles de style CSS2.
Ce type de solution nécessite évidemment un navigateur conforme au standard CSS2, ce qui exclut malheureusement le navigateur utilisé par 95% (environ) des utilisateurs : Internet Explorer. Cependant, il reste possible de construire une solution propre s'affichant correctement dans tous les navigateurs basés sur Gecko (Mozilla, Firebird, ...), et d'externaliser les adaptations spécifiques à Internet Explorer.
Menu basé sur une liste simple
Un menu est à la base une liste d'items à plusieurs niveaux, il est donc possible de la représenter simplement par des entrées HTML <ul> :
<ul> <li>menu1 aa <ul> <li>menu1.1 cccc</li> <li>menu1.2 dddddd</li> </ul> </li> <li>menu2 bbbb <ul> <li>menu2.1 eeee</li> <li>menu2.2 ffffff</li> </ul> </li> </ul>
Affiché par défaut sous la forme :
- menu1 aa
- menu1.1 cccc
- menu1.2 dddddd
- menu2 bbbb
- menu2.1 eeee
- menu2.2 ffffff
L'ajout de bordures permet de mieux représenter la structure du (futur) menu :
#menu1 ul { border:solid 1px green; } #menu1 ul li { border:solid 1px red; }
Les sous-listes doivent être masquées par défaut
(visibility:hidden;
) et leur taille ne doit
pas être prise en compte dans le calcul de la taille
du menu (position:absolute
) :
#menu2 ul { border:solid 1px green; } #menu2 ul li { border:solid 1px red; } #menu2 ul li ul { position:absolute;visibility:hidden; }
Pour un menu horizontal, le premier niveau de liste
doit être sur une seule ligne, ce qui est obtenu
par un display:table-cell
sur les li
de premier niveau, les li
de deuxième niveaux doivent
être en display:block
:
#menu3 ul { border:solid 1px green; } #menu3 ul li { display:table-cell;border:solid 1px red; } #menu3 ul li ul { position:absolute;visibility:hidden; } #menu3 ul li ul li { display:block; }
C'est le moment de passer au comportement dynamique,
les styles de la forme element:hover
permettent de changer de style au survol de la
souris (non supporté par Internet Explorer). On
change donc à visibility:visible;
la
sous liste contenu dans l'entrée li
survolée.
#menu4 ul { border:solid 1px green; } #menu4 ul li { display:table-cell;border:solid 1px red; } #menu4 ul li ul { position:absolute;visibility:hidden; } #menu4 ul li:hover ul { position:absolute;visibility:visible; } #menu4 ul li ul li { display:block; }
Les sous-menus sont encore décalés par le padding par
défaut des listes, ce qui est corrigé par un
padding:0
. La couleur de fond est
transparente, et laisse donc apparaître le texte en
dessous : background-color:lightgrey
.
Un changement de couleur des items au survol est
aussi souhaitable :
li:hover { background-color:grey}
.
#menu5 ul { padding:0;border:solid 1px green; } #menu5 ul li { background-color:lightgrey;display:table-cell;border:solid 1px red; } #menu5 ul li:hover { background-color:grey} #menu5 ul li ul { position:absolute;visibility:hidden; } #menu5 ul li:hover ul { visibility:visible; } #menu5 ul li ul li { display:block; }
Il reste à enlever les bordures inutiles :
#menu6 ul { padding:0; } #menu6 ul li { background-color:lightgrey;display:table-cell; } #menu6 ul li:hover { background-color:grey} #menu6 ul li ul { border:solid 1px black;position:absolute;visibility:hidden; } #menu6 ul li:hover ul { visibility:visible; } #menu6 ul li ul li { display:block; }
Il s'agit évidemment d'une version de base à adapter à la charte graphique du site cible.
Le menu développé dans la première partie est conforme XHTML/CSS et fonctionne parfaitement dans les navigateurs respectant ces standards. Il ne fonctionne donc absolument pas dans Internet Explorer. Heureusement il existe des moyens de contourner les lacunes du navigateur principal du marché, sans (trop) dégrader le code décrit précédemment. La solution retenue consiste à utiliser une extension propriétaire Internet Explorer, les behaviors (comportements), afin d'isoler le code spécifique dans un fichier externe.
Les comportements Internet Explorer permettent de rattacher des
fonctions à des élements de la page via la feuille de style par
une entrée behavior behavior:url('menu1.htc');
:
#menu7 ul { padding:0; } #menu7 ul li { behavior:url('menu1.htc');background-color:lightgrey;display:table-cell; } #menu7 ul li:hover { background-color:grey} #menu7 ul li ul { border:solid 1px black;position:absolute;visibility:hidden; } #menu7 ul li:hover ul { visibility:visible; } #menu7 ul li ul li { display:block; }Le fichier
menu1.htc
contient la définition des fonctions
javascript permettant d'émuler le comportement standard en rattachant
des fonctions aux évènements onmouseenter
et onmouseleave
.
<PUBLIC:COMPONENT> <PUBLIC:ATTACH EVENT="onmouseenter" ONEVENT="showSubMenu()" /> <PUBLIC:ATTACH EVENT="onmouseleave" ONEVENT="hideSubMenu()" /> <SCRIPT LANGUAGE="JScript"> var menuBackground; var menuHighLight = 'rgb(128,128,128)'; function showSubMenu() { // highlight current item menuBackground = style.backgroundColor; style.backgroundColor = menuHighLight; } function hideSubMenu() { // restore background style.backgroundColor = menuBackground; } </SCRIPT> </PUBLIC:COMPONENT>On remarque que la couleur au survol est écrite directe directement dans le fichier htc, il faut donc faire attention de le maintenir en cohérence avec le style CSS. Cette première version se limite à changer la couleur de fond des éléments
li
au survol de la souris :
Il reste à récupérer le sous-menu correspondant à l'entrée, s'il existe, pour l'afficher :
<PUBLIC:COMPONENT> <PUBLIC:ATTACH EVENT="onmouseenter" ONEVENT="showSubMenu()" /> <PUBLIC:ATTACH EVENT="onmouseleave" ONEVENT="hideSubMenu()" /> <SCRIPT LANGUAGE="JScript"> var menuBackground; var menuHighLight = 'rgb(128,128,128)'; function showSubMenu() { // highlight current item menuBackground = style.backgroundColor; style.backgroundColor = menuHighLight; // get sub menu var submenu = getElementsByTagName('ul')[0]; if (submenu) { // show sub menu submenu.style.visibility='visible'; } } function hideSubMenu() { // restore background style.backgroundColor = menuBackground; // hide submenu var submenu = getElementsByTagName('ul')[0]; if (submenu) { submenu.style.visibility='hidden'; } } </SCRIPT> </PUBLIC:COMPONENT>
Le style construit pour Gecko n'est pas rendu de la manière dans
Internet Explorer, il faut donc adapter le style aux spécificités
de ce navigateur. En particulier, le display:table-cell
n'est pas supporté, il faut donc revenir à un display:inline
sans changer la version interprétée par Gecko.
L'une des solutions possibles est d'utiliser une autre extension
propriétaire d'Internet Explorer : les expressions CSS, qui
permettent de spécifier un style sous forme d'expression
javascript : expression('inline')
.
#menu9 ul { padding:0; } #menu9 ul li { behavior:url('menu2.htc');background-color:lightgrey;display:table-cell;display:expression('inline'); } #menu9 ul li:hover { background-color:grey} #menu9 ul li ul { border:solid 1px black;position:absolute;visibility:hidden; } #menu9 ul li:hover ul { visibility:visible; } #menu9 ul li ul li { display:block; }
Les listes sont décalées vers la droite de la marge
par défaut :ul { margin:0;}
.
De plus, elles sont positionnées relativement au texte du
li
parent, il faut les caler à gauche sans
impacter Gecko :left:expression(0);
.
Cela ne suffit pas, en effet, la position des sous-listes est
calculée par rapport au premier élément parent repositionné,
qui devrait être le li
parent :
ul li { position:expression('relative');}
.
Il reste à décaler les sous-listes vers le bas de la hauteur
du li
: top:expression(this.parentNode.offsetHeight+"px");
#menu10 ul { margin:0;padding:0; } #menu10 ul li { behavior:url('menu2.htc');background-color:lightgrey;position:expression('relative');display:expression('inline'); } #menu10 ul>li { display:table-cell; } #menu10 ul li:hover { background-color:grey} #menu10 ul li ul { top:expression(this.parentNode.offsetHeight+"px");left:expression(0);border:solid 1px black;position:absolute;visibility:hidden; } #menu10 ul li:hover ul { visibility:visible; } #menu10 ul li ul li { display:block; }
Résolution du problème des listes de choix
La dernière version du menu semble fonctionner correctement, jusqu'à ce qu'une liste de choix se trouve sous le menu :
Le menu passe sous la liste de choix ! Ce comportement est officiellement reconnu par Microsoft : il vient du fait que les objets
select
, pour une raison
étrange, sont windowed. Cela signifie qu'ils ne
tiennent pas compte du z-index
appliqué aux
autres éléments HTML.
Une explication détaillée est disponible dans le document :
Gestion des plans d'affichage sous Internet Explorer
.
Le contournement est particulièrement, comment dire,
tordu : il s'appuie sur le fait que les objets iframe
sont les seuls à tenir compte du z-index
des objets
windowed et non windowed. Il faut donc insérer
un objet iframe
derrière le menu et devant le
select
:
<PUBLIC:COMPONENT> <PUBLIC:ATTACH EVENT="onmouseenter" ONEVENT="showSubMenu()" /> <PUBLIC:ATTACH EVENT="onmouseleave" ONEVENT="hideSubMenu()" /> <SCRIPT LANGUAGE="JScript"> var menuBackground; var menuHighLight = 'rgb(128,128,128)'; function showSubMenu() { // highlight current item menuBackground = style.backgroundColor; style.backgroundColor = menuHighLight; // get sub menu var submenu = getElementsByTagName('ul')[0]; if (submenu) { // workaround for windowed select object bug // that ignores zindex values var iframe = getElementsByTagName('iframe')[0]; if (!iframe) { iframe = document.createElement('iframe'); iframe.frameBorder=0; iframe.style.display='block'; iframe.style.position='absolute'; iframe.style.top=submenu.offsetTop; iframe.style.left=0; iframe.style.width=submenu.offsetWidth; iframe.style.height=submenu.offsetHeight; insertBefore(iframe, submenu); } // show sub menu submenu.style.visibility='visible'; iframe.style.visibility='visible'; } } function hideSubMenu() { // restore background style.backgroundColor = menuBackground; // hide submenu var submenu = getElementsByTagName('ul')[0]; if (submenu) { submenu.style.visibility='hidden'; getElementsByTagName('iframe')[0].style.visibility='hidden'; } } </SCRIPT> </PUBLIC:COMPONENT>
Il est maintenant possible de réaliser un menu entièrement en CSS. L'utilisation d'une simple liste permet de faciliter l'interprétation des pages par les outils d'accessibilité dédiés aux non ou mal voyants. Cependant, le contournement des déficiences du navigateur majeur du marché demande encore beaucoup d'efforts.
Télécharger le menu exemple : menu.zip.