Une barre de navigation fixe reste en haut de l'écran pendant que le visiteur fait défiler la page : le menu est toujours à portée de clic. Tu vas la construire en HTML et CSS avec position: sticky, puis ajouter en JavaScript deux finitions : une ombre qui apparaît au défilement, et le lien de la section en cours mis en évidence. Durée : environ 30 minutes.

Ce que tu vas construire

  • une barre qui reste collée en haut de l'écran, sur ordinateur comme sur mobile ;
  • des liens vers les sections de la page, avec un défilement fluide ;
  • des titres qui ne se cachent pas sous la barre quand on arrive sur une section ;
  • une ombre sous la barre dès que la page défile ;
  • le lien de la section affichée à l'écran mis en évidence.

Crée un dossier avec index.html, style.css et script.js.

1. Le HTML : un en-tête et des sections

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Barre de navigation fixe</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <header class="site-header">
    <nav class="nav" aria-label="Navigation principale">
      <a href="#accueil" class="nav-logo">Mon portfolio</a>
      <ul class="nav-links">
        <li><a href="#accueil">Accueil</a></li>
        <li><a href="#services">Services</a></li>
        <li><a href="#projets">Projets</a></li>
        <li><a href="#contact">Contact</a></li>
      </ul>
    </nav>
  </header>

  <main>
    <section id="accueil">
      <h1>Bienvenue</h1>
      <p>Fais défiler la page : la barre reste en haut de l'écran.</p>
    </section>
    <section id="services">
      <h2>Services</h2>
      <p>Sites vitrines, applications web, maintenance.</p>
    </section>
    <section id="projets">
      <h2>Projets</h2>
      <p>Une sélection de mes derniers travaux.</p>
    </section>
    <section id="contact">
      <h2>Contact</h2>
      <p>Écris-moi pour parler de ton projet.</p>
    </section>
  </main>

  <script src="script.js"></script>
</body>
</html>

La barre est un <header> qui contient un <nav>. L'attribut aria-label nomme la navigation pour les lecteurs d'écran, ce qui est utile quand une page en compte plusieurs. Chaque lien pointe vers l'id d'une section : href="#projets" mène à <section id="projets">.

2. Le CSS : sticky plutôt que fixed

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  font-family: system-ui, sans-serif;
  line-height: 1.5;
}

/* La barre reste collée en haut de l'écran pendant le défilement. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 10;
  background: #fff;
  transition: box-shadow 0.2s;
}

/* Ajoutée par le JavaScript dès que la page défile. */
.site-header.scrolled {
  box-shadow: 0 2px 12px rgb(0 0 0 / 0.12);
}

.nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 1.5rem;
  max-width: 60rem;
  margin: 0 auto;
  padding: 0.75rem 1rem;
}

.nav-logo {
  font-weight: 700;
  color: #25737d;
  text-decoration: none;
}

.nav-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.nav-links a {
  padding: 0.25rem 0;
  border-bottom: 2px solid transparent;
  color: #333;
  text-decoration: none;
}

.nav-links a:hover,
.nav-links a:focus-visible,
.nav-links a[aria-current="true"] {
  border-bottom-color: #25737d;
  color: #25737d;
}

section {
  min-height: 80vh;
  max-width: 60rem;
  margin: 0 auto;
  padding: 2rem 1rem;
  /* Laisse la place de la barre quand on arrive sur une ancre. */
  scroll-margin-top: 4rem;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

Les points importants :

  • position: sticky et top: 0 : la barre défile normalement, puis se colle en haut de l'écran quand elle l'atteint. Elle garde sa place dans la page : rien ne passe dessous au chargement.
  • z-index: 10 et un fond blanc : le contenu passe sous la barre, pas au travers.
  • scroll-margin-top: 4rem sur les sections : quand on clique sur un lien, le navigateur s'arrête un peu avant la section. Sans lui, son titre finirait caché sous la barre.
  • scroll-behavior: smooth : le défilement vers une section est fluide, sauf pour les personnes qui ont demandé moins d'animations dans leur système (prefers-reduced-motion).
  • flex-wrap: wrap : sur un petit écran, les liens passent sous le logo au lieu de déborder.

Et position: fixed ?

Avec position: fixed, la barre sort du flux de la page : le début du contenu passe dessous, et il faut ajouter à la main un padding-top de la hauteur de la barre, à ajuster dès qu'elle change. sticky évite ce bricolage et fonctionne dans tous les navigateurs actuels. Seule condition : aucun parent de la barre ne doit avoir overflow: hidden, sinon elle ne colle plus.

3. Le JavaScript : l'ombre et le lien actif

const header = document.querySelector('.site-header');
const links = document.querySelectorAll('.nav-links a');

// 1. Une ombre sous la barre dès que la page a défilé.
function updateShadow() {
  header.classList.toggle('scrolled', window.scrollY > 0);
}

window.addEventListener('scroll', updateShadow, { passive: true });
updateShadow();

// 2. Le lien de la section en cours de lecture est mis en évidence.
const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (!entry.isIntersecting) {
      return;
    }

    links.forEach((link) => {
      if (link.getAttribute('href') === '#' + entry.target.id) {
        link.setAttribute('aria-current', 'true');
      } else {
        link.removeAttribute('aria-current');
      }
    });
  });
}, { rootMargin: '-50% 0px -50% 0px' });

document.querySelectorAll('main section').forEach((section) => observer.observe(section));

L'ombre. À chaque défilement, classList.toggle('scrolled', window.scrollY > 0) ajoute la classe si la page a défilé et l'enlève sinon. L'option { passive: true } promet au navigateur que le code n'empêchera pas le défilement : il reste fluide.

Le lien actif. Plutôt que de calculer la position de chaque section à chaque pixel, on laisse IntersectionObserver prévenir le code. Avec rootMargin: '-50% 0px -50% 0px', la zone observée se réduit à une ligne au milieu de l'écran : la section qui croise cette ligne est celle qu'on lit. Son lien reçoit aria-current="true", qui sert à la fois au style et aux lecteurs d'écran.

4. Tester

  • Fais défiler : la barre reste en haut et une ombre apparaît.
  • Clique sur « Projets » : la page défile en douceur, le titre de la section reste visible sous la barre et le lien est souligné.
  • Réduis la fenêtre à la largeur d'un téléphone : les liens passent à la ligne, sans défilement horizontal.

Pour aller plus loin

  • Un menu burger sur mobile : quand les liens deviennent trop nombreux, cache-les derrière un bouton. C'est l'objet du tutoriel menu de navigation responsive.
  • Cacher la barre en descendant : compare window.scrollY à sa valeur précédente, et fais glisser la barre vers le haut (transform: translateY(-100%)) quand on descend, pour la faire revenir dès qu'on remonte.
  • Un lien d'évitement : un lien « Aller au contenu » en tout début de page, visible au focus, permet aux personnes qui naviguent au clavier de sauter le menu.

Tu as construit un portfolio avec cette barre ? Partage-le dans la vitrine des lecteurs.