
本文介绍如何利用原生 wheel 事件精准捕获鼠标滚轮方向,在无滚动条的 React + Framer Motion 页面中实现上下页平滑切换动画,规避 scroll 事件失效问题,并提供防抖、跨浏览器兼容等关键实践建议。
本文介绍如何利用原生 `wheel` 事件精准捕获鼠标滚轮方向,在无滚动条的 react + framer motion 页面中实现上下页平滑切换动画,规避 `scroll` 事件失效问题,并提供防抖、跨浏览器兼容等关键实践建议。
在构建全屏轮播式单页应用(如产品介绍页、演示引导页)时,常需通过鼠标滚轮触发页面切换动画。但一个常见误区是:直接监听 scroll 事件——它仅对实际发生滚动行为的可滚动元素(如 overflow: auto/scroll 的容器)有效。而当所有页面层均设为 position: absolute、父容器未启用滚动时,window.scroll 或 div.onscroll 将完全静默,无法响应用户意图。
此时,应转向更底层、更可靠的 wheel 事件。它在用户转动鼠标滚轮(或触控板滚动)时无论目标元素是否可滚动都会触发,且通过 Event.deltaY 属性提供精确的垂直位移量与方向信号:
以下是一个生产就绪的实现方案,已集成至你的 Framer Motion 动画逻辑中:
function Page({ bgColor = "red", isVisible, isDown, onWheel // ? 关键:将 wheel 处理器透传至 motion.div }) { return ( <motion.div style={{ height: '90vh', width: '100%', backgroundColor: bgColor, position: 'absolute', }} animate={{ opacity: isVisible ? 1 : [1, 0, 0], y: isVisible ? 0 : isDown ? [0, 90, 180, 0] : [0, -90, -180, 0] }} transition={{ delay: isVisible ? 0.15 : 0, duration: 0.5 }} onWheel={onWheel} // ✅ 绑定到绝对定位的 div 上 /> ); } function App() { const [currentPage, setCurrentPage] = useState(0); const [isScrollDirectionUp, setIsScrollDirectionUp] = useState(true); const pagesArr = [ <Page bgColor="blue" isVisible={currentPage === 0} isDown={isScrollDirectionUp} onWheel={handleWheel} />, <Page bgColor="orange" isVisible={currentPage === 1} isDown={isScrollDirectionUp} onWheel={handleWheel} />, <Page bgColor="green" isVisible={currentPage === 2} isDown={isScrollDirectionUp} onWheel={handleWheel} />, <Page bgColor="red" isVisible={currentPage === 3} isDown={isScrollDirectionUp} onWheel={handleWheel} />, ]; const handleWheel = (event: WheelEvent) => { // ? 设置灵敏度阈值,避免微小抖动误触发 const THRESHOLD = 100; // ? 向下滚动 → 下一页 if (event.deltaY > THRESHOLD) { event.preventDefault(); // 阻止默认滚动(尤其当页面意外获得焦点时) setIsScrollDirectionUp(false); setCurrentPage(prev => (prev + 1) % pagesArr.length); } // ? 向上滚动 → 上一页 if (event.deltaY < -THRESHOLD) { event.preventDefault(); setIsScrollDirectionUp(true); setCurrentPage(prev => (prev - 1 + pagesArr.length) % pagesArr.length); } }; // ⚠️ 注意事项与优化建议: // 1. 【防抖必要性】高频 wheel 事件可能在一帧内触发多次,建议使用 useThrottle 或 requestAnimationFrame 包裹 handleWheel // 2. 【跨浏览器兼容】极少数旧版 Safari 可能用 deltaMode 判断单位(line/pixel/page),但现代主流浏览器统一返回像素级 deltaY,可暂不处理 // 3. 【移动端适配】触摸板/触控屏同样触发 wheel,无需额外逻辑;但若需支持纯触摸拖拽,应补充 touchstart/touchmove 事件 // 4. 【焦点管理】确保容器可接收事件:添加 tabIndex={0} 或 CSS outline: none 避免键盘焦点干扰 return ( <div style={{ overflow: 'hidden' }}> {/* 显式禁用文档滚动,强化体验一致性 */} <div style={{ position: 'fixed', zIndex: 3 }}> <button onClick={() => { setIsScrollDirectionUp(true); setCurrentPage(prev => (prev - 1 + pagesArr.length) % pagesArr.length); }}>prev page</button> <button onClick={() => { setIsScrollDirectionUp(false); setCurrentPage(prev => (prev + 1) % pagesArr.length); }}>next page</button> </div> {pagesArr} </div> ); }
核心要点总结:
✅ 事件选择正确性:wheel 是非滚动容器检测滚轮的唯一标准方案,scroll 在此场景下天然失效;
✅ 方向判定可靠性:以 deltaY 符号为主、阈值过滤为辅,兼顾精度与鲁棒性;
✅ 用户体验完整性:event.preventDefault() 防止页面意外滚动,overflow: hidden 锁定根布局;
✅ 可扩展性设计:onWheel 回调集中处理,便于后续接入节流、日志、多设备适配等增强能力。
通过该方案,你不仅能复现按钮点击的动画效果,更能赋予用户“直觉式”滚动导航体验——真正让交互回归物理直觉。