用户浏览长页面时,一旦往下滚动,导航栏就会随着页面滚出可视区域。想跳转到其他栏目,只能手动拖回顶部,体验相当糟糕。固定导航栏(Sticky Header)就是解决这个问题的经典方案:让页面顶部那一行导航在滚动过程中始终可见。本文从纯 CSS 到 JavaScript 方案逐层展开,帮你选对实现方式。

一、最基础的实现:position fixed
position: fixed 是最常见的做法。元素脱离文档流,相对浏览器窗口定位,无论页面怎么滚动都纹丝不动。基本写法如下:
.header {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 60px;
background-color: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
z-index: 999;
}这里有几个细节不能漏掉。width: 100% 或者 left: 0; right: 0; 保证导航栏横向铺满,否则固定定位后的元素宽度会收缩为内容宽度。z-index 必须给一个足够大的值,避免被后续内容的轮播图、弹层盖住。box-shadow 则是加分项,滚动后导航栏和内容之间会有一条分割线,视觉层次更清晰。
但 fixed 有一个绕不开的副作用:元素脱离文档流之后,原本占用的空间被释放,下方的正文内容会整体上移 60 像素,直接钻到导航栏底下被遮住。解决方式是给紧跟着的主体区域加一个等于导航栏高度的上边距:
.main {
margin-top: 60px; /* 与导航栏高度保持一致 */
}这种写法简单直接,兼容性极好,几乎所有浏览器都支持。缺点是导航栏高度一旦改变(比如响应式布局中导航换行变高),margin-top 就要同步修改,维护起来容易漏。如果项目允许,可以用 CSS 变量统一管理高度:
:root {
--header-height: 60px;
}
.header {
height: var(--header-height);
}
.main {
margin-top: var(--header-height);
}二、更优雅的方案:position sticky
position: sticky 可以说是为吸顶导航量身定做的属性。它的行为介于 relative 和 fixed 之间:元素在正常滚动时跟随文档流,一旦滚动到设定的阈值位置,就“粘”在那里不再动。写法也非常简洁:
.header {
position: sticky;
top: 0;
z-index: 999;
background-color: #fff;
}和 fixed 最大的区别在于,sticky 元素始终占据文档流中的位置,不会遮挡下方内容,因此不需要额外补 margin。当向上滚动回顶部时,它又自然回到原来的位置,整个过程没有跳跃感。这就是为什么现代项目里,能用 sticky 就尽量不用 fixed。
不过 sticky 有几个容易踩的坑,一旦失效往往查半天。第一个坑是父元素的 overflow:只要任意祖先元素设置了 overflow: hidden、overflow: auto 或 overflow: scroll,sticky 就可能失效,因为滚动容器变成了那个祖先而不是页面本身。排查时从当前元素一路往上检查所有父级的 overflow 设置。
第二个坑是父元素高度。sticky 的活动范围被限制在父元素内,如果父容器高度只比导航栏高一点点,粘性效果几乎看不出来。所以导航栏的直接父级最好是包裹整个页面内容的大容器。第三个坑是忘记写 top 值,sticky 必须配合 top 才能生效,只写 position: sticky 而不指定 top: 0 是最常见的低级失误。
兼容性方面,sticky 在主流浏览器中早已全面支持,包括移动端的各种 WebView。只有在需要照顾极老的 IE 浏览器时才需要回退方案,此时可以用下面的代码做特性检测:
/* 支持 sticky 的浏览器使用 sticky,否则回退到 fixed */
@supports (position: sticky) {
.header {
position: sticky;
top: 0;
}
}
@supports not (position: sticky) {
.header {
position: fixed;
top: 0;
width: 100%;
}
.main {
margin-top: 60px;
}
}三、进阶玩法:滚动时动态收起导航栏
移动端屏幕寸土寸金,很多网站会把吸顶导航做得更聪明:向下滚动时收起导航栏给内容让路,向上滚动时立刻弹出来。这个效果需要 JavaScript 配合,核心思路是监听 scroll 事件,比较前后两次的滚动位置来判定方向:
let lastY = 0;
const header = document.querySelector('.header');
window.addEventListener('scroll', () => {
const currentY = window.scrollY;
if (currentY > lastY && currentY > 100) {
// 向下滚动且已离开顶部,收起导航栏
header.classList.add('header-hidden');
} else {
// 向上滚动,显示导航栏
header.classList.remove('header-hidden');
}
lastY = currentY;
}, { passive: true });对应的 CSS 通过 transform 实现位移,配合过渡动画让收放更顺滑:
.header {
position: fixed;
top: 0;
width: 100%;
transition: transform 0.3s ease;
}
.header-hidden {
transform: translateY(-100%);
}有两点值得注意。其一,scroll 事件触发频率非常高,回调里不要做重量级计算,事件监听加上 { passive: true } 可以告诉浏览器不会调用 preventDefault,减少滚动卡顿。其二,判定方向时建议加一个阈值(比如上面的 100 像素),避免用户在页面顶部小幅滚动时导航栏频繁抖动。更进一步,可以用 IntersectionObserver 观察一个哨兵元素来代替 scroll 监听,性能表现会更好。
四、方案对比与选型建议
三种方案各有适用场景,简单对比一下:
| 方案 | 是否遮挡内容 | 是否需要 JS | 适用场景 |
|---|---|---|---|
| position: fixed | 是,需补 margin | 否 | 老项目兼容、结构简单的页面 |
| position: sticky | 否 | 否 | 绝大多数现代项目首选 |
| JS 滚动监听 | 视实现而定 | 是 | 需要动态收起等交互效果 |
如果只是单纯让导航栏吸顶,直接用 position: sticky,三行代码搞定,干净利落。如果导航栏需要动态收起、滚动到某个位置后改变样式(比如从透明变白底),那就在 sticky 或 fixed 的基础上叠加 JS 逻辑。另外提醒一句,无论哪种方案,都别忘了我测移动端效果,iOS Safari 的弹性滚动会让 window.scrollY 出现负值,做方向判断时记得处理边界情况。
Sticky Header固定导航栏position sticky修改时间:2026-09-15 04:56:29