如何用react-three-fiber实现相机动画?点击缩放功能遇阻求助
实现带缓动的点击缩放至物体功能
方案一:自定义缓动函数 + 相机插值逻辑
不用依赖额外库,自己实现可控的缓动过渡:
- 先定义常用的缓动函数,比如
easeOutCubic(可以替换成其他缓动曲线):
const easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
- 在组件中维护相机过渡状态,实现帧循环内的平滑插值:
import { useThree, useFrame } from '@react-three/fiber'; import { useState, useRef } from 'react'; import * as THREE from 'three'; function CameraZoomController() { const { camera } = useThree(); const [targetPos, setTargetPos] = useState(camera.position.clone()); const [targetLookAt, setTargetLookAt] = useState(new THREE.Vector3(0,0,0)); const transitionProgress = useRef(0); const isTransitioning = useRef(false); const transitionSpeed = 0.05; // 数值越大过渡越快,可自行调整 // 点击物体触发缩放的核心函数 const zoomToObject = (mesh) => { // 计算物体包围盒,确定相机需要到达的位置 const boundingBox = new THREE.Box3().setFromObject(mesh); const center = boundingBox.getCenter(new THREE.Vector3()); const size = boundingBox.getSize(new THREE.Vector3()); const maxDim = Math.max(size.x, size.y, size.z); // 计算刚好容纳物体的相机距离 const distance = maxDim / (2 * Math.tan((camera.fov * Math.PI) / 360)); // 基于当前相机朝向,生成相机目标位置 const direction = new THREE.Vector3() .subVectors(camera.position, center) .normalize() .multiplyScalar(distance); const newCameraPos = center.clone().add(direction); setTargetPos(newCameraPos); setTargetLookAt(center); isTransitioning.current = true; transitionProgress.current = 0; }; // 帧循环内执行缓动插值 useFrame(() => { if (isTransitioning.current) { transitionProgress.current += transitionSpeed; const easedProgress = easeOutCubic(transitionProgress.current); camera.position.lerp(targetPos, easedProgress); camera.lookAt(targetLookAt); // 过渡完成后终止循环 if (transitionProgress.current >= 1) { isTransitioning.current = false; } } }); return { zoomToObject }; }
- 在物体组件中调用该函数:
const { zoomToObject } = useContext(CameraZoomContext); // 或者直接传递函数 const meshRef = useRef(); <mesh ref={meshRef} onClick={() => zoomToObject(meshRef.current)}> <boxGeometry args={[2,2,2]} /> <meshStandardMaterial color="red" /> </mesh>
方案二:修复drei/CameraControls的代码控制问题
如果想用drei/CameraControls,无需直接设置enabled={false},而是临时禁用用户输入,过渡完成后恢复:
import { useCameraControls } from '@react-three/drei'; import { useState } from 'react'; import * as THREE from 'three'; function ZoomWithCameraControls() { const cameraControls = useCameraControls(); const [isZooming, setIsZooming] = useState(false); const meshRef = useRef(); const zoomToObject = async (mesh) => { const boundingBox = new THREE.Box3().setFromObject(mesh); const center = boundingBox.getCenter(new THREE.Vector3()); const size = boundingBox.getSize(new THREE.Vector3()); const maxDim = Math.max(size.x, size.y, size.z); const distance = maxDim / (2 * Math.tan((cameraControls.camera.fov * Math.PI) / 360)); // 临时禁用用户输入,避免过渡时干扰 cameraControls.enabled = false; setIsZooming(true); // 调用CameraControls自带的平滑过渡方法 await cameraControls.setPosition( center.x + distance, center.y, center.z, true // 启用内置缓动 ); await cameraControls.lookAt(center.x, center.y, center.z, true); // 过渡完成后恢复交互 cameraControls.enabled = true; setIsZooming(false); }; return ( <mesh ref={meshRef} onClick={() => zoomToObject(meshRef.current)}> <sphereGeometry args={[1,32,32]} /> <meshStandardMaterial color="blue" /> </mesh> ); }
核心说明
- 自定义方案完全可控,可自由替换缓动曲线,适合需要定制过渡效果的场景
- 使用
drei/CameraControls时,setPosition和lookAt的第三个参数设为true即可启用内置缓动,临时禁用用户输入不会影响代码控制逻辑
内容的提问来源于stack exchange,提问作者chrysillo
相关产品推荐
相关产品推荐

