
本文详解为何在 useFrame 中直接使用 state.mouse 会导致网格跟随鼠标时在页面滚动后失准,并提供基于原生 mousemove 事件的稳定替代方案。
本文详解为何在 `useframe` 中直接使用 `state.mouse` 会导致网格跟随鼠标时在页面滚动后失准,并提供基于原生 `mousemove` 事件的稳定替代方案。
在 React + Three.js(如 @react-three/fiber)项目中,实现“Mesh 跟随鼠标”效果是常见需求。但许多开发者会遇到一个隐蔽却高频的问题:当页面存在纵向滚动、且相机或 。
根本原因在于:state.mouse(来自 @react-three/fiber 的 useFrame 回调)返回的是标准化设备坐标(NDC),范围为 [-1, 1],它已自动映射到当前 Canvas 的逻辑视口 —— 但该映射默认假设 Canvas 占满整个视口(viewport),且未考虑页面滚动偏移(window.scrollY)。一旦页面发生滚动,Canvas 在页面中的实际像素位置发生变化,而 state.mouse 并不会自动补偿 scrollY,导致计算出的目标屏幕坐标(如 targetY = (mouse.y * viewport.height) / 2)严重偏离真实鼠标位置,最终表现为网格“突然弹回顶部”。
✅ 正确解法:绕过 state.mouse,直接监听原生 mousemove 事件,获取带滚动补偿的绝对页面坐标,再手动转换为 Canvas 内部的局部坐标。
以下是推荐的稳定实现:
import { useRef, useEffect, useState } from 'react';
import { useFrame } from '@react-three/fiber';
export const Example = () => {
const ref = useRef<THREE.Mesh>(null);
const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
// 监听原生 mousemove,自动包含 scrollY 补偿
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
setMousePos({ x: e.clientX, y: e.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
useFrame((state) => {
if (!ref.current || !state.camera || !state.gl.domElement) return;
const canvas = state.gl.domElement;
const rect = canvas.getBoundingClientRect();
// 将 clientX/clientY 转换为 Canvas 局部坐标(归一化到 [-1, 1])
const x = ((mousePos.x - rect.left) / rect.width) * 2 - 1;
const y = -((mousePos.y - rect.top) / rect.height) * 2 + 1; // Y 翻转
// 转换为世界空间(可选:若需精确深度控制,建议用 raycaster;此处简化为平面跟随)
const targetX = x * state.viewport.width / 2;
const targetY = y * state.viewport.height / 2;
// 平滑插值(阻尼跟随)
if (ref.current) {
ref.current.position.x += (targetX - ref.current.position.x) * 0.2;
ref.current.position.y += (targetY - ref.current.position.y) * 0.2;
}
});
return (
<mesh ref={ref}>
<sphereGeometry args={[0.3, 16, 16]} />
<meshNormalMaterial />
</mesh>
);
};? 关键要点与注意事项:
- ✅ getBoundingClientRect() 自动包含滚动偏移,因此 clientX/Y - rect.left/top 是获取 Canvas 内坐标的黄金标准;
- ✅ 注意 Y 轴方向:Canvas 的 y=0 在顶部,而 NDC 中 y=1 在顶部,因此需手动翻转(-y * 2 + 1);
- ⚠️ 若
- ? 如需更高精度(例如与 3D 场景深度联动),应结合 raycaster 投射到特定平面(如 z=0 的 XY 平面),而非仅做二维投影;
- ? 此方案完全脱离 state.mouse,彻底规避其滚动兼容性缺陷,适用于所有含滚动的 SPA 布局(如 Next.js、Vite + React 应用)。
总结:state.mouse 是便捷抽象,但在复杂 DOM 布局下缺乏鲁棒性;掌握原生事件 + 坐标系转换,才是构建生产级交互动画的坚实基础。










