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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 02:17:31