原生JS开发滚动进度条时如何让循环内classList.add事件仅触发一次
问题根因
- 滚动事件触发频率极高,只要滚动位置落在对应区间内,每次滚动都会重复给
.scroll-title绑定transitionend事件,同一个回调被绑定多次后,触发时就会执行多次。 - 同一标题区间内滚动时,会重复执行添加
action类的逻辑,导致动画被反复触发。
解决代码
1. 新增全局变量
// 存储当前激活的标题索引,初始值为0对应默认的MENU let activeIndex = 0; // 提前检测好transition事件名,无需每次滚动重复计算 const transitionEnd = whichTransitionEvent();
2. 修改滚动事件逻辑
window.onscroll = function() { for (var i = 1; i < pageTitlesRif.length; i++) { if (window.pageYOffset > pageTitlesRif[i - 1] && window.pageYOffset < pageTitlesRif[i]) { // 只有切换到新标题区间时才执行动画逻辑 if (i-1 !== activeIndex) { activeIndex = i-1; // 先移除可能残留的事件监听做兜底 titleScroll.removeEventListener(transitionEnd, putClass, false); titleScroll.classList.add('action'); // 绑定一次性事件监听,触发后自动解绑 titleScroll.addEventListener(transitionEnd, putClass, { once: true, passive: true }); titleScroll.innerText = pageTitlesText[activeIndex]; } // 进度条更新逻辑保留,每次滚动更新更顺滑 scroll.style.left = (pageTitlesRif[i-1] * tot / scrollHeight) + 'px'; scroll.style.width = ((pageTitlesRif[i] - pageTitlesRif[i - 1]) * tot / scrollHeight) + 'px'; // 匹配到区间后直接跳出循环,减少不必要的遍历 break; } } }
3. 回调函数增加兜底逻辑(兼容不支持once参数的老旧浏览器)
let putClass = function(){ this.classList.remove('action'); // 主动移除事件监听,避免重复触发 this.removeEventListener(transitionEnd, putClass, false); }
额外优化点
你原有代码中let totSide = bandSide.offsetHeight的bandSide变量未定义,没有用到的话可以直接删除该行,避免控制台报错。
内容的提问来源于stack exchange,提问作者BennyB
相关产品推荐
相关产品推荐

