如何在非可滚动容器中通过鼠标滚轮检测滚动方向并驱动页面切换动画

2次阅读

如何在非可滚动容器中通过鼠标滚轮检测滚动方向并驱动页面切换动画

本文介绍如何利用原生 wheel 事件精准捕获鼠标滚轮方向,在无滚动条的 React + Framer Motion 页面中实现上下页平滑切换动画,规避 scroll 事件失效问题,并提供防抖、跨浏览器兼容等关键实践建议。

本文介绍如何利用原生 `wheel` 事件精准捕获鼠标滚轮方向,在无滚动条的 react + framer motion 页面中实现上下页平滑切换动画,规避 `scroll` 事件失效问题,并提供防抖、跨浏览器兼容等关键实践建议。

在构建全屏轮播式单页应用(如产品介绍页、演示引导页)时,常需通过鼠标滚轮触发页面切换动画。但一个常见误区是:直接监听 scroll 事件——它仅对实际发生滚动行为的可滚动元素(如 overflow: auto/scroll 的容器)有效。而当所有页面层均设为 position: absolute、父容器未启用滚动时,window.scroll 或 div.onscroll 将完全静默,无法响应用户意图。

此时,应转向更底层、更可靠的 wheel 事件。它在用户转动鼠标滚轮(或触控板滚动)时无论目标元素是否可滚动都会触发,且通过 Event.deltaY 属性提供精确的垂直位移量与方向信号:

  • deltaY > 0:向下滚动(多数浏览器,包括 chrome/firefox/edge
  • 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 回调集中处理,便于后续接入节流、日志、多设备适配等增强能力。

通过该方案,你不仅能复现按钮点击的动画效果,更能赋予用户“直觉式”滚动导航体验——真正让交互回归物理直觉。

text=ZqhQzanResources