jQuery / Fixer un menu en haut pendant le défilement
Comparez le défilement du document à la position initiale du menu, puis activez une classe position:fixed à cette limite. Retirez-la au-dessus. Conservez une place de même hauteur pour que la sortie du flux ne fasse pas remonter brusquement le contenu.
Limite stable et espace réservé
CSS position:fixed quitte le flux et se place généralement par rapport à la fenêtre. top:0 est son bord supérieur. La position initiale est une coordonnée du document : alignez les repères.
Ne mesurez pas le menu déjà fixé comme s’il était encore à sa place initiale. #menu-slot reste dans le document. Son top visible plus window.scrollY donne le seuil document.
Le menu fixé peut ne plus suivre la largeur parentale. Affectez left et width depuis le slot, puis conservez sa hauteur avec bordure. Sans marge et en border-box, les limites correspondent.
Exemple complet avec défilement et redimensionnement
Enregistrez tout en UTF-8 sous fixed-menu-demo.html et ouvrez-le dans le navigateur. Internet charge le fichier officiel jQuery 4.0.0. Aucun serveur ou paquet n’est nécessaire. Seuil, position du contenu, largeur et ancres ont été vérifiés dans Edge sous Windows avec 3.7.1 et 4.0.0.
L’en-tête mesure 180px ; le seuil initial vaut donc 180. Commandes et statut inférieurs sont des outils d’exercice à adapter ou retirer sur un vrai site.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>A menu fixed after its original position</title>
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<style>
:root { --menu-height: 60px; }
html { scroll-behavior: auto; }
body { margin: 0; font: 16px/1.5 sans-serif; }
header { height: 180px; display: grid; place-items: center; background: #e7f2f2; }
.wrap { width: 100%; max-width: 800px; padding: 0 20px; margin: 0 auto; box-sizing: border-box; }
#menu { box-sizing: border-box; width: 100%; display: flex; flex-wrap: wrap; gap: 12px; padding: 12px; border: 2px solid teal; background: #fff; }
#menu a { padding: 4px; color: #075c55; }
#menu.is-fixed { position: fixed; top: 0; z-index: 20; box-shadow: 0 3px 8px #0003; }
main section { min-height: 650px; padding-top: 24px; }
main h2 { margin: 0; scroll-margin-top: calc(var(--menu-height) + 16px); }
#controls { position: fixed; right: 8px; bottom: 8px; z-index: 30; background: #fff; padding: 4px; }
button { font: inherit; margin: 2px; }
#result { position: fixed; left: 8px; bottom: 70px; background: #fff; white-space: pre-wrap; padding: 6px; z-index: 30; }
</style>
</head>
<body>
<header>Header before the menu</header>
<div class="wrap"><div id="menu-slot">
<nav id="menu" aria-label="Sections"><a href="#alpha">Alpha section</a><a href="#beta">Beta section</a><a href="#gamma">Gamma section</a></nav>
</div></div>
<main class="wrap">
<section><h2 id="alpha">Alpha</h2><p>First section content.</p></section>
<section><h2 id="beta">Beta</h2><p>Second section content.</p></section>
<section><h2 id="gamma">Gamma</h2><p>Third section content.</p></section>
</main>
<div id="controls"><button type="button" id="down">Go to 300px</button><button type="button" id="reset">Back to 0px</button></div>
<pre id="result" aria-live="polite"></pre>
<script>
$(function () {
const $menu = $("#menu"), $slot = $("#menu-slot");
let scheduled = false;
function update() {
scheduled = false;
const rect = $slot[0].getBoundingClientRect();
const threshold = rect.top + window.scrollY;
const fixed = $(window).scrollTop() >= threshold;
$menu.toggleClass("is-fixed", fixed).css({
left: fixed ? rect.left + "px" : "",
width: fixed ? rect.width + "px" : ""
});
const height = $menu.outerHeight();
$slot.css("height", height + "px");
document.documentElement.style.setProperty("--menu-height", height + "px");
$("#result").text(`menu=${fixed ? "fixed" : "flow"}\nthreshold=${Math.round(threshold)}\nscrollY=${Math.round(window.scrollY)}`);
}
function schedule() {
if (!scheduled) { scheduled = true; window.requestAnimationFrame(update); }
}
$(window).on("scroll.fixedMenuLesson resize.fixedMenuLesson load.fixedMenuLesson", schedule);
$("#down").on("click", function () { window.scrollTo(0, 300); schedule(); });
$("#reset").on("click", function () { window.scrollTo(0, 0); schedule(); });
update();
});
</script>
</body>
</html>
Sortie initiale en haut. flow signifie placement normal.
menu=flow
threshold=180
scrollY=0
- Défilez lentement : flux normal à 179px, fixation à top=0 dès 180px.
- Go to 300px donne menu=fixed, threshold=180, scrollY=300. Le contenu ne doit pas sauter de la hauteur du menu.
- Élargissez puis rétrécissez : bord gauche et largeur suivent le conteneur ; les retours de liens ajustent la hauteur réservée.
- Beta section suit la véritable ancre #beta et garde le titre visible dessous. Les liens restent accessibles au clavier.
- Back to 0px restaure le flux et vide left et width de fixation. Il ne supprime pas le fragment URL existant.
Mises à jour et dégagement des ancres
Comparez $(window).scrollTop() au seuil. .toggleClass("is-fixed", fixed) impose ce booléen. Un simple toggle à chaque scroll alternerait les classes selon le nombre d’événements.
scroll, resize et load programment update() par requestAnimationFrame() avant le prochain affichage. scheduled évite plusieurs rappels en attente. Cela ne garantit ni intervalle en millisecondes ni cadence ; gardez lectures et modifications légères.
La largeur précède la mesure de hauteur pour prendre en compte les retours à la ligne. update() initial traite aussi une position restaurée. Si contenu supérieur ou polices changent ensuite, reliez schedule() à leur fin. Toute modification du DOM n’est pas automatiquement observée.
--menu-height plus 16px définit scroll-margin-top des titres. Les ancres natives ne restent pas cachées. Un autre en-tête fixe demande top et dégagement adaptés. Gardez de vrais href et ne bloquez pas inutilement la navigation.
CSS sticky et contrôles sur site
Pour simplement adhérer en haut, position:sticky avec top peut suffire sans JavaScript. Il conserve l’espace et dépend du conteneur défilant et du bloc englobant. Vérifiez parents courts et ancêtres overflow ; ce n’est pas identique à fixed.
L’exemple vise tout le document, pas un panneau défilant intérieur. Des ancêtres transformés peuvent changer le repère fixed. Examinez la structure plutôt que d’écraser les règles globales position ou overflow.
Un z-index élevé ne résout pas tous les contextes d’empilement ou fenêtres modales. Vérifiez contenu mobile, focus visible et liens au zoom. Supprimez ou déplacez les superpositions d’exercice.
Au retrait, restaurez aussi classe fixe, left/width, hauteur réservée et propriété personnalisée dans le périmètre de la fonction. Des initialisations répétées exigent un cycle de vie évitant les gestionnaires doubles.




