如何根据滚动位置逐像素控制导航栏显隐实现跟手滚动动画
逐像素跟手的滚动导航栏实现方案
常见跳变方案的问题
目前网上大部分导航栏滚动隐藏的实现,都是通过判断滚动方向后直接切换固定CSS样式实现,代码如下:
var lastScrollTop; // 存储上一次滚动位置 navbar = document.getElementById('navbar'); // 获取导航栏元素 window.addEventListener('scroll',function(){ var scrollTop = window.pageYOffset || document.documentElement.scrollTop; if(scrollTop > lastScrollTop){ // 向下滚动时直接隐藏 navbar.style.top='-80px'; } else{ // 向上滚动时直接显示 navbar.style.top='0'; } lastScrollTop = scrollTop; });
配套的基础样式:
body{ margin:0; padding:0; } .container{ background:url('https://images.unsplash.com/photo-1497250681960-ef046c08a56e') no-repeat center center; height:170vh; } #navbar{ position:fixed; top:0; left:0; width:100%; background:blue; border-radius:0 0 30px 30px; color:white; text-align:center; height:80px; transition: 0.5s; } footer{ text-align:center; position:relative; bottom:0; left:0; }
基础HTML结构:
<div class="container"> <navbar id="navbar"> <h1>NAVBAR<h1> </navbar> </div> <footer> <h3>End of the Page</h3> </footer>
这类方案的问题很明显:导航栏的显示/隐藏是跳变的,即使加了CSS过渡,也无法做到和用户滚动操作完全同步的跟手效果。
跟手效果实现逻辑
要实现滚动1像素导航栏同步移动1像素的效果,核心是放弃方向判断后的固定样式切换,把滚动差值直接映射为导航栏的偏移量,遵循以下规则:
- 导航栏高度固定为80px,初始状态完全可见,偏移量为0
- 每次滚动时计算本次滚动的距离差值:向下滚动差值为正,向上滚动差值为负
- 导航栏偏移量和滚动差值反向计算:向下滚多少像素,导航栏就向上偏移多少像素(隐藏对应距离);向上滚多少像素,导航栏就向下偏移多少像素(显示对应距离)
- 偏移量始终限制在
[-80px, 0]区间内:最小偏移-80px(完全隐藏),最大偏移0(完全显示),不会出现超出范围的异常位置
最终实现代码
优化后的JS逻辑
这里用transform: translateY实现偏移,比修改top性能更好,不会触发页面重排;滚动事件加passive标记提升滚动响应速度:
const navbar = document.getElementById('navbar'); const NAV_BAR_HEIGHT = 80; // 和CSS中导航栏高度保持一致 let lastScrollY = window.scrollY; let currentNavOffset = 0; // 记录导航栏当前偏移量,初始为0完全可见 window.addEventListener('scroll', () => { const currentScrollY = window.scrollY; const scrollDelta = currentScrollY - lastScrollY; // 计算本次滚动的距离差 // 更新导航栏偏移量 currentNavOffset -= scrollDelta; // 钳制偏移量在合法区间 currentNavOffset = Math.max(-NAV_BAR_HEIGHT, Math.min(0, currentNavOffset)); // 应用偏移,不需要加CSS过渡,实时跟手 navbar.style.transform = `translateY(${currentNavOffset}px)`; lastScrollY = currentScrollY; }, { passive: true });
调整后的CSS
移除原方案中导航栏的transition属性(加过渡会导致跟手延迟),增加性能优化配置:
body{ margin:0; padding:0; } .container{ background:url('https://images.unsplash.com/photo-1497250681960-ef046c08a56e') no-repeat center center; height:170vh; } #navbar{ position:fixed; top:0; left:0; width:100%; background:blue; border-radius:0 0 30px 30px; color:white; text-align:center; height:80px; will-change: transform; /* 提示浏览器提前做GPU渲染优化 */ } footer{ text-align:center; position:relative; bottom:0; left:0; }
HTML结构不需要改动,和之前保持一致即可。
效果参考

如果需要进一步优化性能,可以把滚动事件里的DOM更新逻辑放到requestAnimationFrame中执行,避免短时间内多次触发重绘。
内容的提问来源于stack exchange,提问作者Niklas
相关产品推荐
相关产品推荐

