react-three/fiber搭配OrbitControls正交相机如何约束y轴拖拽物体到x/z平面
问题根因
原有坐标转换逻辑沿用了透视相机的射线计算规则,且依赖全局窗口尺寸而非Canvas实际渲染尺寸,硬编码的z轴距离计算无法适配正交相机的投影特性、角度变化和缩放调整。
修复方案
使用Three.js内置的Raycaster与地面x-z平面求交的方式获取鼠标对应世界坐标,无需手动计算向量适配相机参数,直接替换Obj.jsx代码即可:
import React, { useState, useMemo } from "react"; import { useDrag } from "@use-gesture/react"; import { animated, useSpring } from "@react-spring/three"; import { useThree } from "@react-three/fiber"; import * as THREE from "three"; function Obj({ setIsDragging }) { const { camera, size, raycaster } = useThree(); const [pos, setPos] = useState([0, 1.5, 0]); const aspect = size.width / size.height; // 定义地面所在的x-z平面,法线向上,平面方程为y=0 const groundPlane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const [spring, api] = useSpring(() => ({ position: pos, scale: 1, rotation: [0, 0, 0], config: { friction: 10 } })); const bind = useDrag( ({ active, xy: [x, y], movement: [mx, my] }) => { if (active) { // 转换为Three.js要求的标准化设备坐标 const mouse = new THREE.Vector2( (x / size.width) * 2 - 1, -(y / size.height) * 2 + 1 ); // 从相机发出射线 raycaster.setFromCamera(mouse, camera); // 计算射线和地面平面的交点 const intersectPoint = new THREE.Vector3(); raycaster.ray.intersectPlane(groundPlane, intersectPoint); if (intersectPoint.isFinite()) { const newPos = [intersectPoint.x, 1.5, intersectPoint.z]; setPos(newPos); api.start({ position: newPos, scale: 1.2, rotation: [my / aspect, mx / aspect, 0] }); } } else { api.start({ position: pos, scale: 1, rotation: [0, 0, 0] }); } setIsDragging(active); } ); return ( <animated.mesh {...spring} {...bind()} castShadow> <dodecahedronBufferGeometry castShadow attach="geometry" args={[1.4, 0]} /> <meshNormalMaterial attach="material" /> </animated.mesh> ); } export default Obj;
实现说明
- 内置的射线求交逻辑自动适配正交相机的任意角度、缩放参数,无需额外矫正系数
- 采用
useThree返回的Canvas实际尺寸计算坐标,避免Canvas非全屏时出现坐标偏移 - 移除了冗余的手动向量计算逻辑,所有坐标转换由Three.js原生方法保证准确性
- 修复了
useState异步更新导致的位置更新滞后问题,计算结果直接同步到弹簧动画
内容的提问来源于stack exchange,提问作者Korimako
相关产品推荐
相关产品推荐

