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

3次阅读

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

本文介绍如何利用原生 wheel 事件精准捕获鼠标滚轮方向,替代无效的 scroll 监听,在无滚动条的全屏叠布局中实现基于滚轮的页面切换动画(支持 Framer Motion 动画逻辑)。

本文介绍如何利用原生 `wheel` 事件精准捕获鼠标滚轮方向,替代无效的 `scroll` 监听,在无滚动条的全屏堆叠布局中实现基于滚轮的页面切换动画(支持 framer motion 动画逻辑)。

在 React 应用中,当多个

position: absolute 堆叠、整体容器本身不可滚动(即未触发原生 scroll 事件)时,常见的 window.addEventListener(‘scroll’, …) 将完全失效——因为页面并未发生滚动,只是用户习惯性地滚动鼠标滚轮。此时,真正应监听的是更底层的 wheel 事件:它在任意可聚焦元素上(包括 或任意 div)均可触发,且携带精确的方向与幅度信息(deltaY),与是否启用滚动条无关。

✅ 正确方案:监听 wheel 事件并解析 deltaY

wheel 事件的 event.deltaY 是核心判断依据:

  • deltaY > 0:表示向下滚动(常见于鼠标滚轮向下、触控板下滑);
  • deltaY 向上滚动(常见于鼠标滚轮向上、触控板上滑);
  • 数值大小反映滚动强度,可用于设置灵敏度阈值,避免误触发。

以下为关键改造代码(已整合进你的 Framer Motion 页面动画逻辑):

function Page({    bgColor = "red",    isVisible,    isDown,    onWheel // ? 新增 prop:透传 wheel 处理函数 }) {   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} // ? 绑定到 motion.div,确保事件被捕获     />   ); }  function App() {   const [currentPage, setCurrentPage] = useState(0);   const [isScrollDirectionUp, setIsScrollDirectionUp] = useState(true);    // ✅ 核心:wheel 事件处理器(带防抖与阈值)   const handleWheel = useCallback((event: WheelEvent) => {     event.preventDefault(); // 阻止默认行为(如页面意外滚动)      const THRESHOLD = 80; // 滚动灵敏度阈值(单位:px),可根据体验调整      if (event.deltaY > THRESHOLD) {       // 向下滚动 → 下一页       setIsScrollDirectionUp(false);       setCurrentPage(prev => (prev + 1) % 4);     } else if (event.deltaY < -THRESHOLD) {       // 向上滚动 → 上一页       setIsScrollDirectionUp(true);       setCurrentPage(prev => (prev - 1 + 4) % 4); // 避免负数取模问题     }   }, []);    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}      />   ];    return (     <div        style={{          overflow: 'hidden', // 确保容器自身不产生滚动条         height: '100vh',         position: 'relative'       }}       onWheel={handleWheel} // ? 也可绑定到根 div,更健壮(推荐)     >       <div style={{ position: "fixed", zIndex: 3 }}>         <button onClick={() => {           setIsScrollDirectionUp(true);           setCurrentPage(prev => (prev - 1 + 4) % 4);         }}>prev page</button>         <button onClick={() => {           setIsScrollDirectionUp(false);           setCurrentPage(prev => (prev + 1) % 4);         }}>next page</button>       </div>       {pagesArr}     </div>   ); }

⚠️ 注意事项与最佳实践

  • event.preventDefault() 必须调用:否则在某些浏览器/设备上可能触发意外的页面滚动或缩放。
  • 阈值(THRESHOLD)至关重要:deltaY 在不同设备(鼠标/触控板/触摸屏)上量级差异极大。建议从 50–100 起步,通过真实设备测试微调;过低易误触发,过高则响应迟钝。
  • 防抖非必需但强烈推荐:高频 wheel 事件(尤其触控板惯性滚动)可能导致连续触发。可结合 useRef + setTimeout 实现简易节流:
    const wheelTimeout = useRef<NodeJS.Timeout | null>(null); const handleWheel = useCallback((e: WheelEvent) => {   if (wheelTimeout.current) clearTimeout(wheelTimeout.current);   wheelTimeout.current = setTimeout(() => {     // 执行页面切换逻辑   }, 100); }, []);
  • 跨浏览器兼容性:现代浏览器对 deltaY 符号定义一致(向下为正),但旧版 safari 可能需兼容 deltaMode。生产环境建议使用 normalizeWheel 工具函数统一标准化。
  • 无障碍考量:纯滚轮导航对键盘/屏幕阅读器用户不友好,请务必保留按钮操作,并为 div 添加 tabIndex={0} 和 role=”application” 提升可访问性。

通过 wheel 事件精准捕获用户意图,你无需依赖 dom 滚动状态,即可在任意静态布局中复现流畅的“滚轮翻页”交互动效——这正是现代沉浸式单页应用(如产品介绍页、简历展示页)的关键技术支点。

text=ZqhQzanResources