La plupart du temps, les solutions de navigation sur petits écrans utilisent du Javascript pour faire apparaître ou disparaître le menu quand on clique sur un bouton. C’est souvent top. On clique sur l’hamburger et hop, le menu glisse du haut de l’écran, d’un des côtés ou se déroule juste sous le logo.

Le problème c’est que sur certains sites que je visite souvent, il faut que j’attende un moment avant de pouvoir naviguer. Surtout sur mobile. Je tape l’adresse et la page apparaît avec le bouton en haut à droite. Je sais où je vais donc je clique le bouton, et j’attends. Je clique encore. Rien. Je regarde en bas de mon écran près de l’adresse… En fait, la page n’est pas encore totalement chargée. Il faut que j’attende que les scripts du bas de la page soient chargés et exécutés. Super …

Sur un site que je visite pour la première fois, ça ne pose pas de problème car j’ai le temps. J’explore, je découvre. Je découvre le logo, puis je lis le texte de l’entête. Puis je défile un peu pour lire le contenu et voir où je suis tombé. Quand j’ai fini, je remonte vers le menu pour l’ouvrir et voir ce qu’on me propose.

Par contre quand je connais bien le site et que j’y vais souvent, ça m’agace un peu de devoir attendre trois bonnes secondes avant d’aller où je veux. Surtout si le contenu est déjà dispo. Cliquer plusieurs fois sur un bouton inactif c’est pas top niveau UX, quand même.

Dans cet article, je vais vous montrer comment coder une navigation mobile sans Javascript. Juste de l’HTML et du CSS. Ce qui veut dire que dès que l’HTML et le CSS sont chargés, le bouton est dispo. Pas besoin d’attendre que le script du bas de la page soit chargé. Une navigation fiable et rapide.

Voilà une vidéo qui vous résume un peu le principe. Il n’y a pas tous les styles décrits ci-dessous, mais l’essentiel y est. Si vous voulez juste lire tout le code, continuez.

D’abord l’HTML

Pour la démo, on va utiliser un document HTML tout ce qu’il y a de plus simple et standard.

<!DOCTYPE html>
<html class="no-js">
<head>
	<meta charset="utf-8" />
	<meta name="viewport" content="width=device-width, initial-scale=1" />
	<link rel="stylesheet" href="style.css" type="text/css" />
    <link rel="stylesheet" href="navigation.css" type="text/css">
    <link href="https://fonts.googleapis.com/css?family=Proza+Libre:400,400i" rel="stylesheet">
</head>

<body>
    <header class="main-header">

        <nav class="main-navigation">
            <div class="nav-wrapper">
                <p class="site-title">Pure CSS mobile navigation</p>

                <ul class="menu">
                    <li><a href="">Menu item 1</a></li>
                    <li><a href="">Menu item 2</a></li>
                    <li><a href="">Menu item 3</a></li>
                    <li><a href="">Menu item 4, but with a lot more text</a></li>
                </ul>
            </div> 
        </nav>

        <div class="hero-section">
            <h1 class="hero-title">Pure CSS mobile navigation</h1>
            <p class="hero-description">Pas besoin de Javascript pour créer une navigation mobile responsive.</p>
        </div>
        
    </header>

    <main class="main-content">
        <p>Problems look mighty small from 150 miles up.</p>
        <p>Spaceflights cannot be stopped. This is not the work of any one man or even a group of men. It is a historical process which mankind is carrying out in accordance with the natural laws of human development.</p>
        <p>A Chinese tale tells of some men sent to harm a young girl who, upon seeing her beauty, become her protectors rather than her violators. That's how I felt seeing the Earth for the first time. I could not help but love and cherish her.</p>
    </main>

    <footer class="main-footer">
        <div class="footer-wrapper">
            <p>This is just the footer. There is nothing to see here.</p>
        </div>
    </footer>
</body>

 

Dans la balise <head>, je charge deux feuilles de styles. La première, style.css, contient juste quelques lignes pour normaliser les styles par défaut et rendre la démo un peu plus jolie. Rien de super palpitant.

La deuxième feuille de style est celle dans laquelle on va bosser et ajouter le CSS qui va rendre notre navigation fonctionnelle. Pour l’instant, elle est vide.

Enfin, je charge une police de caractère pour faire joli. Optionnel, mais sympa pour la démo.

Dans la balise <body>, on commence avec un <header> avec deux éléments : notre barre de navigation <nav class="main-navigation"> avec le titre du site et le menu, puis une entête <div class="hero-section"> avec le titre et un sous-titre. Tout simple.

Puis une zone de contenu <main class="main-content"> avec trois paragraphes de Space Ipsum, et un <footer class="main-footer"> qui n’a rien de spécial.

Les quelques styles présents dans style.css sont d’ordre purement cosmétiques. Si vous parcourez style.css, vous verrez qu’il n’y a que des styles pour normaliser et quelques ajouts simples de padding, marges et typographie.

On va travailler uniquement dans navigation.css. Et comme on va s’appliquer à créer une navigation pour mobile, on va réduire la fenêtre du navigateur jusqu’à la taille d’un smartphone pour commencer.

Principe et mise en oeuvre

Ce qu’on veut, c’est faire un bouton apparent sur mobiles qui va permettre d’afficher ou de cacher la navigation. On cherche donc à gérer uniquement deux états: affiché/caché.

Pour ça, on va utiliser une simple checkbox, car une checkbox a uniquement deux états: coché ou non, et on va se servir de ces deux états pour afficher ou cacher le menu voisin dans l’HTML.

Dans index.html, on va donc ajouter notre checkbox juste à côté de la liste <ul class="menu">, et on va en profiter pour lui rajouter une classe pour pouvoir la cibler dans notre feuille de styles.

<input type="checkbox" class="menu-checkbox" />
<ul class="menu">
	<li><a href="">Menu item 1</a></li>
	…
</ul>

 

Si on rafraîchit notre page, on peut voir la checkbox, mais elle ne sert à rien pour l’instant. Il faut maintenant dire à notre feuille de style de cacher le menu par défaut, et de le montrer uniquement quand la checkbox est cochée.

Dans navigation.css, pour que le menu soit caché par défaut, on va ajouter:

.menu {
    display: none;
}

 

Puis, on va utiliser une pseudo classe :checked et un sélecteur CSS de ninja pour sélectionner le menu <ul class="menu"> suivant la checkbox uniquement quand celle-ci est cochée.

.menu-checkbox:checked ~ .menu {
    display: block;
}

 

Avec le « ~ » on sélectionne les éléments ayant pour classe .menu précédés d’un élément ayant pour classe .menu-checkbox. Avec la pseudo classe :checked, on s’assure de sélectionner .menu uniquement si .menu-checkbox est cochée.

Et voilà ! C’est fonctionnel ! En cliquant la checkbox, on fait apparaître et disparaître le menu !

Un beau bouton

Bon, notre navigation fonctionne, mais c’est pas très joli. On va améliorer tout ça.

Déjà, je vais remplacer la checkbox par un bouton hamburger en utilisant l’entité HTML &equiv; , avec un petit texte. Pour ça, on va utiliser un élément <label>.

On va simplement placer un <label> et le lier à la checkbox. En liant le <label> à la checkbox, on va pouvoir cocher/décocher la checkbox en cliquant sur ce <label>. Pour ça, on va ajouter à notre checkbox un identifiant correspondant à l’attribut for du <label>.

<input type="checkbox" id="menu-checkbox" class="menu-checkbox">
<label for="menu-checkbox" class="menu-toggle">≡ Menu</label>
<ul class="menu">
	<li><a href="">Menu item 1</a></li>
	…
</ul>

 

Maintenant, quand on clique le mot ‘Menu’ ou l’icône du burger, le menu apparaît. On peut donc supprimer cette checkbox. On pourrait supprimer la checkbox avec un simple display: none; mais cela pose un problème d’accessibilité sur desktop. Le problème ne se pose pas sur mobile, mais si vous voulez garder votre navigation cachée derrière un bouton sur grand écran, il vaut mieux éviter.

En effet, un display: none; supprime aussi la checkbox pour la navigation au clavier, et même si on peut cliquer un <label> pour cocher la checkbox, un <label> ne peut pas recevoir de focus. Il est donc inopérable au clavier. Il a besoin de sa checkbox accessible. Accessible pas visuellement, mais au moins au clavier. On la cache donc avec un position: absolute;

.menu-checkbox {
	opacity: 0;
	position: absolute;
	top: -1000px;
}

 

Par contre, pour mettre en évidence que la checkbox est sous focus pour les utilisateurs navigant au clavier, on va utiliser un autre sélecteur de ninja.

.menu-checkbox:focus + .menu-toggle {
    outline: #00A1A1 auto 5px;
}

.menu-toggle {
    padding: .5em 1em;
}

 

Ici, on sélectionne notre <label> avec la classe .menu-toggle s’il est adjacent (« + ») à notre checkbox cochée et on lui ajoute une outline. Ce qui veut dire que le <label> aura un joli cadre quand la checkbox recevra le focus. Un utilisateur voyant, mais naviguant au clavier saura donc qu’il peut ouvrir le menu quand il aura placé le focus sur la checkbox.

Un peu de mise en page

C’est déjà bien mieux. La checkbox a disparu, et on a un bouton à la place.

Mais on va mettre ce bouton en haut à droite, aligné avec le titre. On pourrait faire un simple float pour ça, mais on va faire mieux. On va utiliser flexbox.

En utilisant display: flex; sur notre <div class="nav-wrapper"> (qui deviendra le conteneur flex), on va pouvoir lui dire d’afficher ses enfants en file indienne de gauche à droite.

Dans notre navigation.css, on ajoute:

.nav-wrapper {
    display: flex;
}

 

Et hop, le bouton se retrouve collé au titre, juste à sa droite. On va utiliser les propriétés justify-content et align-items pour s’assurer que le bouton aille bien se caler à droite et soit parfaitement aligné verticalement avec le titre.

.nav-wrapper {
    align-items: center;
    display: flex;
    justify-content: space-between;
}

 

justify-content: space-between; va permettre d’espacer le plus possible les éléments à l’intérieur de .nav-wrapper, et align-items: center; va permettre de les centrer verticalement.

Maintenant, on a un problème. Quand on clique le bouton pour faire apparaître le menu, celui-ci apparaît à droite au lieu d’en-dessous !

Evidemment ! On a demander à .nav-wrapper d’aligner ses enfants grâce à display: flex, qu’il y en ait deux, trois, ou même dix!

On peut sortir le menu du flow du document avec un position:absolute; , ou alors utiliser les propriétés de flexbox pour régler le problème.

On va simplement dire à .nav-wrapper de renvoyer ses enfants à la ligne en-dessous quand il n’arrive pas à tous les caser sur la même ligne sans les écraser un peu.

.nav-wrapper {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
}

 

Sur tout petit écran, ça marche sans soucis. Notre menu est renvoyé en-dessous car il n’y a plus de place pour lui sur la ligne au-dessus. Il se comporte comme du texte simple, en fait. Et l’espace supérieur est utilisé complètement par le titre et le bouton.

Mais si on agrandit la fenêtre du navigateur, le menu vient se remettre sur la ligne supérieure dés qu’il en a la place. Plus le menu est large, plus il lui faut de place, mais ça finit toujours par arriver.

J’ai mis un long lien en quatrième position exprès pour vous montrer. Si vous réduisez ce lien en coupant le texte, le menu prends moins de place et a plus de facilités à venir se recaler en haut à côté de notre bouton.

Et on veut qu’il reste en-dessous, quelle que soit sa taille. Donc, pour être sûr que .menu reste en-dessous sur une nouvelle ligne quelle que soit sa largeur, on va lui réserver toute la largeur du conteneur avec flex-basis.

.menu {
    flex-basis: 100%;
}

 

Ici, on demande à .nav-wrapper (le conteneur flex) de réserver 100% de sa largeur à son enfant .menu. Il tombera donc toujours en-dessous. Ouf, le menu reste en-dessous quelle que soit la largeur de la fenêtre.

Transitions

Notre menu fonctionne correctement, mais passer de display: none; à display: block; c’est un peu abrupte. On va embellir et fluidifier tout ça.

D’abord, concernant notre menu, on va enlever notre display: none; par défaut pour le remplacer par :

.menu {
    flex-basis: 100%;
    max-height: 0;
    overflow: hidden;
}

Puis on remplace notre display: block; quand la checkbox est cochée par:

.menu-checkbox:checked + .menu {
    max-height: 1000px;
}

Au lieu de supprimer complètement le menu, on lui donne une hauteur nulle pour le cacher.

Mais le soucis, c’est qu’il a beau avoir une hauteur nulle, il conserve ses marges, et c’est pour ça que la barre de navigation est agrandie. En inspectant le code, on voit bien les marges de notre menu.

On va donc lui enlever ses marges et les lui remettre quand il est ouvert.

.menu {
    flex-basis: 100%;
    margin: 0;
    max-height: 0;
    overflow: hidden;
}

.menu-checkbox:checked + .menu {
    margin: 1em 0;
    max-height: none;
}

Pour dérouler le menu de façon fluide, on va ajouter une transition sur les marges, l’opacité, et la hauteur.

.menu {
    margin: 0;
    max-height: 0;
    opacity: 0;
    overflow: hidden;
    transition: margin .5s ease-in-out, max-height .5s ease-in-out, opacity .3s .1s ease-in-out,
}

.menu-checkbox:checked + .menu {
    margin: 1em 0;
    max-height: 1000px;
    opacity: 1;
}

Le soucis d’accessibilité au clavier se pose encore ici. Les éléments du menu ont beau être cachés visuellement, ils sont accessibles au clavier !

Ce qui veut dire que si l’on met en focus le bouton du menu, puis qu’on continue à appuyer sur TAB, on parcourt les liens de notre menu sans le savoir, alors qu’on devrait directement tomber sur le premier élément hors de ce menu qui peut recevoir le focus.

On va donc rajouter un simple visibility: hidden; quand le menu est caché. visibility permet de cacher/montrer aussi les liens de notre menu aux screen readers.

.menu {
    margin: 0;
    max-height: 0;
    opacity: 0;
    overflow: hidden;
    transition: margin .5s ease-in-out, max-height .5s ease-in-out, opacity .3s .1s ease-in-out;
    visibility: hidden;
}

.menu-checkbox:checked + .menu {
    margin: 1em 0;
    max-height: 500px;
    opacity: 1;
    visibility: visible;
}

Maintenant, en appuyant sur TAB, je tombe au-delà du menu s’il est fermé. S’il est ouvert, je peux parcourir les liens.

Maintenant, on va rajouter quelques styles pour rendre le menu plus joli.

.menu {
    flex-basis: 100%;
    list-style: none;
    margin: 0;
    max-height: 0;
    opacity: 0;
    overflow: hidden;
    padding: 0 2em;
    text-transform: uppercase;
    transition: margin .5s ease-in-out, max-height .5s ease-in-out, opacity .3s .1s ease-in-out;
}

.menu li {
    border-bottom: 1px solid #eee;
}

.menu li a {
    display: inline-block;
    padding: 0.5em 1em;
}

Voilà, c’est suffisant pour notre exemple.

Le menu sur desktop

Maintenant, on va s’occuper de notre menu sur grand écran. Pour l’instant, le menu garde le même aspect et garde son bouton sur grand écran.

On va faire en sorte que le bouton disparaisse et que les liens du menu s’affichent en ligne, à droite de la barre de navigation.

On va donc travailler dans une media query, et on va commencer par supprimer le bouton et rendre au menu sa visibilité.

@media screen and (min-width: 65em) {
    .menu {
        margin: 0;
        max-height: 1000px;
        opacity: 1;
        padding: 0;
        visibility: visible;
    }

    .menu-toggle,
    .menu-checkbox {
        display: none;
    }
}

 

Quand on agrandi la fenêtre, notre menu apparaît bien, mais il reste en-dessous du titre. C’est parce qu’on lui a réservé 100% de la largeur. Il faut lui demander de prendre uniquement la place dont il a besoin.

@media screen and (min-width: 65em) {
    .menu {
        flex-basis: auto;
        margin: 0;
        max-height: 1000px;
        opacity: 1;
        padding: 0;
        visibility: visible;
    }
}

Et voilà. Maintenant, on n’a plus qu’à enlever les bordures et mettre les liens en file indienne. On va réduire aussi le padding pour que les liens soient un peu plus serrés.

    .menu li {
        border: none;
        display: inline-block;
    }

    .menu li a {
        padding: .5em;
    }

Conclusion

Notre menu est fonctionnel et surtout il est immédiatement accessible car il ne nécessite aucun Javascript !

J’espère que cette démo vous a aidé et vous a inspiré ! J’ai plein d’astuces comme celle-ci à vous montrer pour éviter au maximum le JavaScript qui ralentit vos pages et rendre votre site ultra rapide et lean.

Quand on y pense bien, c’est juste une checkbox et quelques lignes de CSS. Pas de JS ni pour les fonctionnalités ni pour les animations, pas de float dans des conteneurs aux dimensions hard codées.

Il faut admettre que Flexbox aide énormément pour ce genre de mise en page. Ce n’est pas pour rien que le thème actuellement sur ce site utilise uniquement Flexbox, et quasiment aucun script !

Personnellement, même si j’adore le JavaScript, j’évite au maximum d’en utiliser pour les raisons évoquées en introduction. La seule chose importante que cette navigation ne fait pas, c’est communiquer aux screen readers le changement d’état du menu. Mais ça, ça nécessite du JavaScript. Et en plus, l’utilisateur peut savoir si la checkbox est cochée ou non.

Je suis un gros gros fan de Flexbox, des animations et transitions CSS et de ce genre d’astuces de ninja, donc attendez-vous à en lire d’autres dans de prochains articles. En attendant, inscrivez-vous pour n’en rater aucune !

 


e7d666b23c04d381b6c7387fc3e156c8WWWWWWWWWWWWWWWWWWWWWWWWWWWWWW