You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Three Fiber实现模型X、Z轴拖拽跟随鼠标的问题

解决Three.js(react-three-fiber)中模型无法跟随鼠标拖拽的问题

你的核心问题在于:直接将鼠标移动量按固定比例叠加到模型位置的方式,没有考虑相机的投影转换(透视/正交),导致模型移动和鼠标位置无法精准对应,自然做不到"完全跟随"的效果。

修正方案:用射线投射(Raycaster)实现精准拖拽

通过Three.js的Raycaster,可以将屏幕上的鼠标坐标转换为3D空间中的位置,让模型严格跟随鼠标在指定平面上移动。以下是修改后的完整代码:

import { useThree, useRaycaster, useMouse } from '@react-three/fiber';
import { useGesture } from '@use-gesture/react';
import { useState, useRef } from 'react';
import * as THREE from 'three';

const BasicOrangeBox = ({ setControlsDisabled, startPosition }) => {
  const { camera } = useThree();
  const raycaster = useRaycaster();
  const mouse = useMouse();
  const [boxPosition, setBoxPosition] = useState(startPosition);
  // 保存拖拽开始时,模型与鼠标交点的偏移量,避免模型跳位
  const dragOffset = useRef({ x: 0, z: 0 });

  const bind = useGesture({
    onDragStart: () => {
      setControlsDisabled(true);
      // 计算拖拽初始时的射线与模型所在平面的交点
      raycaster.setFromCamera(mouse, camera);
      // 定义拖拽平面:这里固定Y轴位置为模型初始Y值,即模型在XZ平面移动
      const dragPlane = new THREE.Plane(new THREE.Vector3(0, 1, 0), -startPosition.y);
      const intersection = new THREE.Vector3();
      raycaster.ray.intersectPlane(dragPlane, intersection);
      // 记录模型位置与交点的偏移
      dragOffset.current.x = boxPosition.x - intersection.x;
      dragOffset.current.z = boxPosition.z - intersection.z;
    },
    onDrag: () => {
      raycaster.setFromCamera(mouse, camera);
      const dragPlane = new THREE.Plane(new THREE.Vector3(0, 1, 0), -startPosition.y);
      const intersection = new THREE.Vector3();
      // 计算当前鼠标对应的3D平面位置
      if (raycaster.ray.intersectPlane(dragPlane, intersection)) {
        setBoxPosition(prev => ({
          ...prev,
          x: intersection.x + dragOffset.current.x,
          z: intersection.z + dragOffset.current.z
        }));
      }
    },
    onDragEnd: () => {
      setControlsDisabled(false);
      // 更新初始基准位置
      setBoxPosition(prev => ({ ...prev, x0: prev.x, z0: prev.z }));
    }
  });

  return (
    <mesh
      {...bind()}
      position={[boxPosition.x, boxPosition.y, boxPosition.z]}
    >
      <boxGeometry />
      <meshBasicMaterial color={"orange"} />
    </mesh>
  );
};

关键改进点说明

  1. 射线投射转换:Raycaster会根据相机参数,将屏幕鼠标坐标映射到3D空间的指定平面上,确保模型移动和鼠标位置完全匹配,不受相机缩放、视角变化影响。
  2. 拖拽偏移处理:在拖拽开始时记录模型与鼠标交点的偏移,避免模型瞬间跳转到鼠标位置,让拖拽操作更自然。
  3. 可自定义拖拽平面:如果需要让模型在XY平面(垂直方向)移动,只需修改平面的法向量:
    // XY平面拖拽:固定Z轴位置
    const dragPlane = new THREE.Plane(new THREE.Vector3(0, 0, 1), -startPosition.z);
    

原代码的核心问题

原代码中newObj.x = newObj.x0 + (x/100)这种固定比例累加的方式,本质是将屏幕像素移动量直接映射为3D单位,完全忽略了相机的投影关系——当相机拉近/拉远、视角改变时,这个比例会彻底失效,模型移动和鼠标操作的对应关系会完全错乱。

内容的提问来源于stack exchange,提问作者LCAYSS

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 23:25:27