如何用原生JavaScript实现导航栏点击平滑滚动效果
纯JS实现导航点击平滑滚动方案
原有代码问题排查
- 点击事件逻辑未闭合,缺少滚动执行、激活态样式添加的核心逻辑,代码截断直接导致功能失效
- a标签硬编码内联
onclick事件,和后续绑定的监听逻辑重复,href="#"默认跳顶部的行为容易和事件监听冲突 - 直接调用原生滚动API时未计算固定导航栏的高度偏移,滚动后区块顶部被导航栏遮挡,会造成导航栏"消失"的视觉错觉
- 未控制JS执行时机,若脚本在DOM渲染前加载,会因为获取不到元素直接报错,功能完全不生效
完整实现代码
function navBar() { const sections = document.getElementsByTagName('section'); const navBarUL = document.getElementById("navBarULID"); // 获取导航栏元素,用于后续高度计算 const navEle = document.querySelector('header'); let navHeight = navEle.offsetHeight; // 生成导航项,移除冗余内联事件 for (let i = 0; i < sections.length; i++) { const newLI = document.createElement("li"); // 用data属性存储对应section的ID,比硬编码索引更稳定 const link = `<a href="javascript:void(0)" data-target="section${i+1}" id='link_no${i+1}'> Section ${i+1} </a>`; newLI.innerHTML = link; navBarUL.appendChild(newLI); } /** * 自定义平滑滚动函数,不依赖CSS原生平滑特性、不依赖scrollIntoView原生平滑参数 * @param {HTMLElement} targetElement 滚动目标元素 */ function smoothScrollTo(targetElement) { // 目标滚动位置 = 元素距文档顶部距离 - 导航栏高度,预留导航显示空间 const targetPosition = targetElement.offsetTop - navHeight; const startPosition = window.pageYOffset; const distance = targetPosition - startPosition; // 滚动总时长,单位ms,可自行调整滚动速度 const duration = 600; let startTime = null; // easeInOutQuad缓动函数,实现自然的加减速滚动效果 function ease(t, b, c, d) { t /= d / 2; if (t < 1) return c / 2 * t * t + b; t--; return -c / 2 * (t * (t - 2) - 1) + b; } // 逐帧执行滚动动画 function animation(currentTime) { if (startTime === null) startTime = currentTime; const timeElapsed = currentTime - startTime; const nextScrollY = ease(timeElapsed, startPosition, distance, duration); window.scrollTo(0, nextScrollY); if (timeElapsed < duration) requestAnimationFrame(animation); } requestAnimationFrame(animation); } // 绑定导航项点击事件 for (let i = 0; i < sections.length; i++) { const currentLink = document.getElementById('link_no' + (i + 1)); currentLink.addEventListener('click', function(e) { e.preventDefault(); // 实时更新导航高度,适配导航栏滚动时缩放的场景 navHeight = navEle.offsetHeight; // 清除所有导航项的激活样式,倒序遍历避免动态集合索引错乱 const allActiveLinks = document.getElementsByClassName('active-link'); for (let j = allActiveLinks.length - 1; j >= 0; j--) { allActiveLinks[j].classList.remove('active-link'); } // 为当前点击项添加激活样式 this.classList.add('active-link'); // 获取对应目标区块 const targetId = this.getAttribute('data-target'); const targetSection = document.getElementById(targetId); // 清除所有区块的激活样式 for (let k = 0; k < sections.length; k++) { sections[k].classList.remove('your-active-class'); } // 为当前目标区块添加激活样式 targetSection.classList.add('your-active-class'); // 执行自定义平滑滚动 smoothScrollTo(targetSection); }) } } // DOM完全加载完成后再初始化导航,避免获取元素失败 document.addEventListener('DOMContentLoaded', navBar);
关键逻辑说明
- 滚动偏移计算:每次点击时实时获取导航栏高度,滚动位置减去导航高度,彻底避免区块内容被导航遮挡、导航视觉上消失的问题
- 纯JS动画实现:基于
requestAnimationFrame做逐帧滚动,搭配缓动函数实现自然的平滑效果,不需要依赖CSSscroll-behavior: smooth属性,也不需要调用scrollIntoView的平滑滚动参数 - 遍历优化:删除激活样式时采用倒序遍历,适配
getElementsByClassName返回动态HTMLCollection的特性,避免正序删除导致的索引错乱、样式清除不彻底问题 - 稳定性优化:用
data-target属性存储对应区块ID,代替硬编码索引,后续增减区块时不需要调整索引逻辑;href使用javascript:void(0)彻底阻断默认锚点跳转行为 - 执行时机控制:将导航初始化绑定在
DOMContentLoaded事件上,确保DOM渲染完成后再执行元素获取逻辑,避免脚本报错导致功能失效
注意事项
- 请确保CSS中没有给
html、body标签设置scroll-behavior: smooth属性,完全通过JS实现滚动逻辑,符合需求要求 - 可通过修改
duration参数调整滚动速度,数值越大滚动动画时长越长 - 若导航栏为静态定位(非fixed固定),可直接将
navHeight设为0,不需要做偏移计算
内容的提问来源于stack exchange,提问作者srohome
相关产品推荐
相关产品推荐

