如何实现随页面滚动基于vh触发样式变化的菜单栏?
基于vh滚动位置切换菜单栏样式实现方案
这个效果不需要引入第三方动画库,原生浏览器API配合几十行代码就能稳定实现,性能比直接监听scroll事件计算位置更好。
核心逻辑
- 提前定义不同滚动区间对应的菜单栏样式状态,比如:
- 0-100vh(首屏范围):菜单栏透明、无阴影、浅色文字
- 100vh-200vh:菜单栏白色实底、投影、深色文字
- 200vh以上:菜单栏缩小尺寸、深色实底、浅色文字
- 用原生
IntersectionObserver监听提前放置在对应vh位置的埋点元素,埋点进入/离开视口触发线时,直接切换菜单栏的类名即可,不需要高频计算滚动距离。
完整实现代码
HTML结构
<!-- 固定顶部的菜单栏 --> <nav id="siteNav" class="nav"> <div class="logo">站点LOGO</div> <div class="nav-menu">首页 | 功能 | 关于 | 联系</div> </nav> <!-- 状态切换埋点,放在对应vh高度位置,不需要可见 --> <div class="scroll-point" data-target-vh="100" style="position: absolute; top: 100vh; visibility: hidden;"></div> <div class="scroll-point" data-target-vh="200" style="position: absolute; top: 200vh; visibility: hidden;"></div> <!-- 测试用占位内容,模拟长页面 --> <div style="height: 400vh; padding-top: 100px;"> <p>页面内容区域,向下滚动即可看到菜单栏样式切换效果</p> </div>
CSS样式
/* 菜单栏基础样式,固定在页面顶层 */ .nav { position: fixed; top: 0; left: 0; width: 100%; display: flex; justify-content: space-between; align-items: center; padding: 24px 48px; z-index: 999; /* 加过渡让样式切换更顺滑,不需要额外写keyframes */ transition: all 0.25s ease; } /* 首屏透明状态 */ .nav.status-transparent { background: transparent; color: #ffffff; } /* 滚动过100vh的浅色状态 */ .nav.status-light { background: #ffffff; color: #1a1a1a; box-shadow: 0 2px 16px rgba(0,0,0,0.06); padding: 16px 48px; } /* 滚动过200vh的深色缩小状态 */ .nav.status-dark { background: #1a1a1a; color: #ffffff; padding: 12px 48px; }
JavaScript逻辑
const nav = document.getElementById('siteNav'); // 初始化首屏默认状态 nav.classList.add('status-transparent'); // 初始化滚动观察器 const scrollObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { const triggerVh = entry.target.dataset.targetVh; // 先清空所有状态类,避免类名叠加导致样式混乱 nav.classList.remove('status-transparent', 'status-light', 'status-dark'); if (entry.isIntersecting) { // 向下滚动碰到埋点,切换到对应状态 switch(triggerVh) { case '100': nav.classList.add('status-light'); break; case '200': nav.classList.add('status-dark'); break; } } else { // 向上滚动离开埋点,切回上一级状态 switch(triggerVh) { case '100': nav.classList.add('status-transparent'); break; case '200': nav.classList.add('status-light'); break; } } }) }, { // 触发线设置在视口最顶部,滚动到顶部碰到埋点立刻触发 rootMargin: '0px 0px -100% 0px' }) // 注册所有埋点到观察器 document.querySelectorAll('.scroll-point').forEach(point => { scrollObserver.observe(point); })
常见问题说明
- 之前实现失败大概率是三个原因:一是直接绑定scroll事件做计算,触发频率太高逻辑容易出bug还会卡顿;二是IntersectionObserver没有设置正确的rootMargin,触发时机不对;三是切换类名时没有清理旧的状态类,导致样式冲突。
- 如果需要新增更多样式切换节点,只需要新增一个对应top值为目标vh的埋点元素,补充对应的CSS状态类和JS判断逻辑即可,扩展非常方便。
- 所有状态切换的动画都通过CSS的transition实现,不需要额外写复杂的keyframes动画,性能更好维护也简单。
内容的提问来源于stack exchange,提问作者Spench
相关产品推荐
相关产品推荐

