在react-three-fiber中点击mesh对象实现相机对焦缩放的方法
实现方案
不需要额外引入第三方依赖,你现有依赖中的OrbitControls配合three.js原生相机API即可实现该功能,也可以直接用项目已安装的react-spring实现平滑的变焦过渡效果。
前置修正
首先修正你现有代码中的小问题:你同时import Box from './Box'又在当前文件定义了同名的Box组件,会导致冲突,保留其中一个即可,以下示例以保留当前文件定义的Box组件为准。
最简实现(无动画直接跳转)
import React, { useEffect, useRef, useState } from 'react'; import * as THREE from 'three'; import './App.scss'; import { Canvas, useFrame, useThree } from "react-three-fiber"; import { OrbitControls, Stars } from '@react-three/drei'; // 封装相机控制逻辑组件,放在Canvas内部才可获取场景上下文 const CameraController = ({ targetMesh }) => { const { camera } = useThree(); const controlsRef = useRef(); useEffect(() => { if (!targetMesh) return; // 获取点击物体的世界坐标 const meshWorldPos = new THREE.Vector3(); targetMesh.getWorldPosition(meshWorldPos); // 设置轨道控制器的聚焦点为物体坐标 controlsRef.current.target.copy(meshWorldPos); // 定义相机偏移距离,数值越小距离物体越近、变焦倍数越大 const cameraOffset = 8; // 设置相机位置,你也可以根据需求调整偏移方向,不一定是Z轴正方向 camera.position.set(meshWorldPos.x, meshWorldPos.y, meshWorldPos.z + cameraOffset); controlsRef.current.update(); }, [targetMesh, camera]) return <OrbitControls ref={controlsRef} /> } const Box = ({ position, args, color, onMeshClick }) => { const mesh = useRef(null); return ( <mesh position={position} ref={mesh} onClick={() => onMeshClick(mesh.current)} > <boxBufferGeometry attach='geometry' args={args} /> <meshStandardMaterial attach='material' color={color} /> </mesh> ); } export default function App() { const [selectedMesh, setSelectedMesh] = useState(null); return ( <> <Canvas colorManagement> <Stars /> <ambientLight intensity={1.6} /> <Box position={[2, 0, 0]} args={[3, 3, 2]} color="green" onMeshClick={setSelectedMesh} /> <Box position={[-2, 0, 0]} args={[1, 2, 1]} color="red" onMeshClick={setSelectedMesh} /> <CameraController targetMesh={selectedMesh} /> </Canvas> </> ); }
平滑过渡变焦实现
如果你需要更流畅的动画效果,可以直接用项目已安装的react-spring实现,仅需修改CameraController组件:
import { useSpring } from '@react-spring/core' const CameraController = ({ targetMesh }) => { const { camera } = useThree(); const controlsRef = useRef(); // 定义弹簧动画配置 const [anim, api] = useSpring(() => ({ target: [0, 0, 0], cameraPos: [0, 0, 10], config: { mass: 1, tension: 200, friction: 20 } })) useEffect(() => { if (!targetMesh) return; const meshWorldPos = new THREE.Vector3(); targetMesh.getWorldPosition(meshWorldPos); const cameraOffset = 8; // 触发动画更新 api.start({ target: [meshWorldPos.x, meshWorldPos.y, meshWorldPos.z], cameraPos: [meshWorldPos.x, meshWorldPos.y, meshWorldPos.z + cameraOffset] }) }, [targetMesh, api]) // 每帧更新控制器和相机位置 useFrame(() => { controlsRef.current.target.set(...anim.target.get()); camera.position.set(...anim.cameraPos.get()); controlsRef.current.update(); }) return <OrbitControls ref={controlsRef} /> }
扩展优化
如果需要适配不同尺寸的物体,保证物体完整显示在视野中,可以通过物体的包围球半径动态计算相机偏移距离:
// 放在useEffect里获取包围球半径 targetMesh.geometry.computeBoundingSphere(); const boundingRadius = targetMesh.geometry.boundingSphere.radius; // 根据包围球半径和相机视场角计算合适的偏移距离 const cameraOffset = boundingRadius / Math.tan((camera.fov * Math.PI) / 360) * 1.2;
内容的提问来源于stack exchange,提问作者Mir Tanvir Ahmed
相关产品推荐
相关产品推荐

