如何实现仅给当前视口内的section元素添加active类,解决多元素同时激活bug
问题原因分析
- 重复绑定滚动事件:循环内给每个section都单独绑定scroll监听器,5个section就绑定了5次,性能损耗大且逻辑容易冲突
- 视口尺寸是固定值:初始化时就获取了宽高,后续如果窗口大小改变,判断基准就会出错
- 类名操作方法错误:
removeAttribute("class", "active your-active-class")写法不符合API规范,removeAttribute只能接收一个参数,作用是删除整个class属性,无法删除指定类名 - 没有全局去重逻辑:当前逻辑只要元素满足视口条件就加类,没有在加类前先把所有section的active类都移除,自然会出现多个元素同时带active类的情况
- 判断条件过于严格:要求元素完全在视口内才加类,实际滚动时很容易出现多个元素同时满足(比如小屏设备、section高度很小的场景),可根据需求调整判定阈值
修复后代码
// 提前获取所有section节点转成数组 const sectionListArray = Array.from(document.querySelectorAll('section')); function setActiveSection() { // 每次触发先清除所有section的active类,保证只会有一个生效 sectionListArray.forEach(section => section.classList.remove('active', 'your-active-class')); // 实时获取当前视口尺寸,适配窗口缩放场景 const viewHeight = document.documentElement.clientHeight; const viewWidth = document.documentElement.clientWidth; for (const section of sectionListArray) { const rect = section.getBoundingClientRect(); // 这里用的是元素经过视口中心就判定为激活,你也可以换回原完全进入视口的判断逻辑 const isInView = rect.top < viewHeight / 2 && rect.bottom > viewHeight / 2; if (isInView) { section.classList.add('active', 'your-active-class'); // 找到符合的就退出循环,保证只有一个元素加类 break; } } } // 全局只绑定一次滚动事件,加防抖避免高频触发提升性能 let scrollTimer = null; document.addEventListener('scroll', () => { if (scrollTimer) clearTimeout(scrollTimer); scrollTimer = setTimeout(setActiveSection, 100); }); // 页面初始化时执行一次,保证首屏section默认加类 setActiveSection();
核心优化说明
- 全局仅绑定1次滚动事件,配合100ms防抖逻辑,大幅降低性能损耗
- 每次判断前先清空所有section的active类,从根源上避免多个元素同时带active类的问题
- 改用
classListAPI操作类名,符合规范,不会误删元素上的其他自定义类 - 找到符合视口条件的元素后直接跳出循环,进一步优化执行效率
- 视口尺寸每次判断时实时获取,适配窗口缩放的场景
- 优化了视口判定逻辑,交互体验更流畅,你也可以根据需求换回完全进入视口的判断规则
内容的提问来源于stack exchange,提问作者Noha derwa
相关产品推荐
相关产品推荐

