应使用 intersection observer 监听占位元素是否进入视口顶部来控制导航栏收缩,而非直接监听 scroll 事件;需设置明确高度的稳定触发器、配合 position: sticky 与 css class 切换实现平滑过渡,并用 width: 100vw 和 body padding-top 避免布局错位。

用 Intersection Observer 监听滚动位置而非监听 scroll 事件
直接绑 scroll 事件做高度切换,容易卡顿、抖动,尤其在低端安卓或旧版 iOS 上。Intersection Observer 是异步的,不触发重排,性能更稳,且天然支持“进入/离开视口”语义。
关键不是监听滚动本身,而是观察一个**占位元素是否进入视口顶部**——比如在导航栏下方紧贴放一个 <div id="sticky-trigger"></div>,当它被滚到视口顶部时,说明用户已开始下拉,此时收缩导航;当它完全离开视口(isIntersecting === false 且 boundingClientRect.top > 0),再恢复原高。
- 必须给触发器设明确高度(哪怕
1px),否则IntersectionObserver可能无法触发回调 - 不要把触发器放在
position: sticky元素内部——它的位置会随粘性行为浮动,导致判断失准 -
threshold: [0]足够,不需要设多个阈值;避免用rootMargin模拟偏移,容易和实际滚动位置错位
导航栏用 position: sticky + 动态 class 切换高度
position: sticky 本身不支持动态改高,但它能保证元素始终“锚定”在 top: 0 位置。真正收缩靠的是 JS 切换 class,然后用 CSS 过渡控制 height、padding、font-size 等属性。
别用 transform: scale() 或 max-height 动画来模拟收缩——前者会导致文字模糊、点击区域错位;后者在 height 从 auto 变化时无法过渡。稳妥做法是显式定义两个高度状态:
- 默认状态:
.nav--expanded { height: 72px; padding: 0 24px; } - 收缩状态:
.nav--condensed { height: 48px; padding: 0 16px; } - 加
transition: height 0.3s ease, padding 0.3s ease;,不要写all,避免意外动画
移动端缩放和 viewport 下 width 必须用 100vw
在 iOS Safari 或部分安卓浏览器里,用户双指缩放页面后,width: 100% 会按缩放前的视口宽度计算,导致导航栏左右溢出或留白。这是固定定位常见但极易被忽略的坑。
- 导航栏必须设
width: 100vw,不是100%,也不是fit-content - 同时加
left: 0; right: 0;防止因父容器 margin 或 padding 偏移 - 如果用了
transform(如轮播图动画)或filter(如毛玻璃效果)在 body 或 header 上,fixed/sticky 元素可能 z-index 失效——显式加z-index: 1000并确认父级没设overflow: hidden
body 需预留 padding-top 防内容遮挡
导航栏一旦变成 position: fixed 或 sticky,就脱离文档流。如果没处理,第一屏内容会被盖住——这不是 bug,是预期行为。
- 给
加padding-top: 72px(对应展开高度),而不是给主内容区加 margin - 这个值不能写死:如果导航高度响应式变化(比如横屏变矮),padding-top 也得同步更新——用 JS 在 class 切换时同步改
document.body.style.paddingTop - 别用
margin-top给导航本身,position: fixed下 margin 不参与布局计算,只造成视觉偏移
transform、overflow: hidden 或 flex 的 align-items 干扰;否则 Intersection Observer 返回的 boundingClientRect 坐标会失真,导致收缩时机早于或晚于真实滚动位置。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











