jQuery / Fixar um menu no topo ao rolar
Compare a rolagem do documento com a posição original do menu e ative uma classe position:fixed ao alcançá-la. Remova ao voltar acima. Preserve espaço da mesma altura para que a saída do fluxo não puxe o conteúdo para cima.
Limite estável e espaço reservado
CSS position:fixed sai do fluxo e normalmente se posiciona em relação à janela. top:0 é sua borda superior. A posição original pertence ao documento; use coordenadas compatíveis.
Não meça o menu já fixado como posição original. #menu-slot permanece no documento. Seu top visível mais window.scrollY fornece o limite documental.
O menu fixo pode deixar de acompanhar a largura do pai. Defina left e width pelo espaço e preserve sua altura com borda. Sem margem e com border-box, os limites coincidem.
Exemplo completo de rolagem e tamanho
Salve tudo em UTF-8 como fixed-menu-demo.html e abra no navegador. A Internet carrega o arquivo oficial do jQuery 4.0.0. Não exige servidor nem pacotes. Limite, posição do conteúdo, mudanças de largura e âncoras foram conferidos no Edge para Windows com 3.7.1 e 4.0.0.
O cabeçalho tem 180px, portanto o limite inicial é 180. Controles e estado inferiores são recursos de prática a adaptar ou remover.
<!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>
Saída inicial no topo. flow significa posição normal.
menu=flow
threshold=180
scrollY=0
- Role devagar: fluxo normal em 179px e fixação em top=0 a partir de 180px.
- Go to 300px resulta em menu=fixed, threshold=180 e scrollY=300. O conteúdo não deve saltar uma altura de menu.
- Amplie e reduza: esquerda e largura acompanham o contêiner; quebras dos links ajustam a altura reservada.
- Beta section segue a âncora real #beta com título visível abaixo. Links mantêm foco e teclado normal.
- Back to 0px restaura fluxo e limpa left e width fixos. Não remove o fragmento URL existente.
Atualizações e espaço para âncoras
Compare $(window).scrollTop() ao limite. .toggleClass("is-fixed", fixed) define explicitamente o booleano. Toggle simples em todo scroll alternaria classes conforme a quantidade de eventos.
scroll, resize e load agendam update() com requestAnimationFrame() antes da próxima pintura. scheduled evita callbacks pendentes duplicados. Não garante milissegundos nem taxa de quadros; mantenha leves leituras e alterações.
A largura vem antes da medição de altura para considerar quebras de links. update() inicial trata posições restauradas. Se conteúdo superior ou fontes mudarem depois, conecte schedule() ao concluir. Nem toda mudança do DOM é observada automaticamente.
--menu-height mais 16px define scroll-margin-top dos títulos. Âncoras nativas não ficam cobertas. Outro cabeçalho fixo exige top e espaço adaptados. Preserve href reais sem impedir navegação desnecessariamente.
CSS sticky e verificações reais
Para aderir simplesmente ao topo, position:sticky e top podem bastar sem JavaScript. Preserva espaço e depende do contêiner de rolagem e bloco envolvente. Confira pais curtos e ancestrais overflow; não equivale a fixed.
O exemplo rola o documento inteiro, não um painel interno. Ancestrais transformados podem mudar a referência fixed. Examine a estrutura sem sobrescrever regras globais position ou overflow.
z-index alto não resolve todo modal ou contexto de empilhamento. Confira conteúdo móvel, foco visível e links com zoom. Remova ou reposicione sobreposições de prática.
Ao desmontar, restaure classe fixa, left/width, altura e propriedade personalizada no âmbito da função, além dos eventos. Inicializar repetidamente exige ciclo de vida que evite duplicações.




