onWheel事件优化:实现滚动时立即触发方法而非停止后执行
嘿,我完全懂你遇到的这个糟心问题——连续滚动滚轮时,scrollIntoView的平滑动画根本没机会启动,非得等滚轮停了才动,这体验太别扭了。本质原因是:浏览器在处理连续的wheel事件时,会不断触发你的事件处理函数,而原生的平滑滚动动画是异步的,新的事件会直接打断或覆盖掉之前的动画任务,导致它永远没机会执行。
给你两个靠谱的解决方案,按需选就行:
方案一:用「锁机制」保护原生平滑滚动
核心思路是加一个标志位,确保同一时间只有一个滚动动画在执行,避免连续事件打断它:
// 标记当前是否正在执行滚动动画 let isScrolling = false; window.addEventListener("wheel", e => { // 如果正在滚动,直接忽略当前事件 if (isScrolling) { e.preventDefault(); return; } e.preventDefault(); const delta = e.detail ? e.detail * (-120) : e.wheelDelta; const direction = delta > 0 ? 'up' : 'down'; let targetElement; if (direction === "down") { targetElement = document.querySelector('.who'); } else if (direction === "up") { targetElement = document.querySelector('.why'); } if (targetElement) { isScrolling = true; // 执行原生平滑滚动 targetElement.scrollIntoView({ alignToTop: true, behavior: 'smooth'}); // 监听动画结束事件,重置锁 const onScrollFinish = () => { isScrolling = false; targetElement.removeEventListener('transitionend', onScrollFinish); targetElement.removeEventListener('webkitTransitionEnd', onScrollFinish); // 兼容Safari等webkit内核浏览器 }; targetElement.addEventListener('transitionend', onScrollFinish); targetElement.addEventListener('webkitTransitionEnd', onScrollFinish); } });
这个方案的好处是:
- 保留了浏览器原生的平滑滚动效果,代码简洁
- 通过锁机制避免了连续事件的干扰,滚动触发时机变得即时
方案二:手动控制滚动动画(兼容性更好)
如果你的项目需要兼容不支持scrollIntoView平滑特性的旧浏览器,或者想要更自定义的滚动效果,可以用requestAnimationFrame手动实现滚动:
let isScrolling = false; const SCROLL_STEP = 60; // 每次滚动的步长,数值越大滚动越快 window.addEventListener("wheel", e => { if (isScrolling) { e.preventDefault(); return; } e.preventDefault(); const delta = e.detail ? e.detail * (-120) : e.wheelDelta; const direction = delta > 0 ? 'up' : 'down'; let targetOffset; if (direction === "down") { targetOffset = document.querySelector('.who').offsetTop; } else if (direction === "up") { targetOffset = document.querySelector('.why').offsetTop; } if (targetOffset !== undefined) { isScrolling = true; const currentOffset = window.scrollY; const totalDistance = targetOffset - currentOffset; const totalSteps = Math.abs(totalDistance) / SCROLL_STEP; let currentStep = 0; const animateScroll = () => { currentStep++; // 计算当前要滚动到的位置 const currentPosition = currentOffset + (totalDistance / totalSteps) * currentStep; window.scrollTo(0, currentPosition); if (currentStep < totalSteps) { requestAnimationFrame(animateScroll); } else { // 最后一步确保精准滚动到目标位置 window.scrollTo(0, targetOffset); isScrolling = false; } }; requestAnimationFrame(animateScroll); } });
这个方案的优势是:
- 不依赖浏览器原生的平滑滚动API,兼容性拉满
- 可以自由调整滚动速度、缓动效果(比如加个缓动函数)
- 完全不受连续
wheel事件的干扰,触发即时性拉满
内容的提问来源于stack exchange,提问作者devBem
相关产品推荐
相关产品推荐

