
本文介绍如何利用原生 wheel 事件精准捕获鼠标滚轮方向,替代无效的 scroll 监听,在无滚动条的全屏堆叠布局中实现基于滚轮的页面切换动画(支持 Framer Motion 动画逻辑)。
本文介绍如何利用原生 `wheel` 事件精准捕获鼠标滚轮方向,替代无效的 `scroll` 监听,在无滚动条的全屏堆叠布局中实现基于滚轮的页面切换动画(支持 framer motion 动画逻辑)。
在 React 应用中,当多个
✅ 正确方案:监听 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 滚动状态,即可在任意静态布局中复现流畅的“滚轮翻页”交互动效——这正是现代沉浸式单页应用(如产品介绍页、简历展示页)的关键技术支点。











