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

React Three Fiber结合React Spring与Orbit Controls时from值不更新如何解决

解决方案

你的问题核心是useSpring的from配置仅会在组件首次渲染时计算一次,后续用户通过OrbitControls修改相机位置、控制点后,from的值不会自动同步最新状态,导致每次动画都从初始记录的位置启动。

修改后的完整代码如下:

import { useRef, useEffect, useState } from 'react';
import { useThree, useFrame } from '@react-three/fiber';
import { OrbitControls } from '@react-three/drei';
import { useSpring, easings } from '@react-spring/three';

function Controls({ cameraData, duration }) {
  const [orbit, setOrbit] = useState(true);
  const [target, setTarget] = useState(cameraData.lookAt);
  const { gl, camera } = useThree();
  // 新增OrbitControls ref,用于获取用户操作后的实时控制点
  const controlsRef = useRef(null);

  // 改用带api的useSpring写法,支持手动触发动画、动态配置参数
  const [springProps, api] = useSpring(() => ({
    config: { duration: duration ?? 1000, easing: easings.easeCubic },
    // 初始配置仅在首次渲染生效
    from: {
      x: camera.position.x,
      y: camera.position.y,
      z: camera.position.z,
      lookAtX: target[0],
      lookAtY: target[1],
      lookAtZ: target[2],
    },
    to: {
      x: cameraData.position[0],
      y: cameraData.position[1],
      z: cameraData.position[2],
      lookAtX: cameraData.lookAt[0],
      lookAtY: cameraData.lookAt[1],
      lookAtZ: cameraData.lookAt[2],
    },
    onStart: () => setOrbit(false),
    onRest: () => {
      setOrbit(true);
      setTarget(cameraData.lookAt);
    },
  }));

  // 监听cameraData变化,每次触发动画前读取当前最新的相机和控制点状态作为from
  useEffect(() => {
    api.start({
      from: {
        x: camera.position.x,
        y: camera.position.y,
        z: camera.position.z,
        // 从controls ref拿实时的控制点,不要读取不存在的camera.lookAt属性
        lookAtX: controlsRef.current?.target.x ?? target[0],
        lookAtY: controlsRef.current?.target.y ?? target[1],
        lookAtZ: controlsRef.current?.target.z ?? target[2],
        // 如果需要避免起始和结束值一致导致动画不触发,可以给每个from值减0.1,和你原逻辑保持一致
      },
      to: {
        x: cameraData.position[0],
        y: cameraData.position[1],
        z: cameraData.position[2],
        lookAtX: cameraData.lookAt[0],
        lookAtY: cameraData.lookAt[1],
        lookAtZ: cameraData.lookAt[2],
      }
    });
  }, [cameraData, api, camera, target]);

  useFrame(() => {
    if (!orbit) {
      // 不要访问内部属性_value,用官方提供的get()方法获取当前弹簧值
      camera.position.set(
        springProps.x.get(),
        springProps.y.get(),
        springProps.z.get()
      );
      const lookAtX = springProps.lookAtX.get();
      const lookAtY = springProps.lookAtY.get();
      const lookAtZ = springProps.lookAtZ.get();
      camera.lookAt(lookAtX, lookAtY, lookAtZ);
      // 同步更新controls的target,避免动画结束后controls跳回旧位置
      controlsRef.current?.target.set(lookAtX, lookAtY, lookAtZ);
    }
  });

  return (
    <OrbitControls
      ref={controlsRef}
      enabled={orbit}
      target={target}
      args={[camera, gl.domElement]}
    />
  );
}

核心修改说明

  • 改用[springProps, api]的useSpring写法,支持手动调用api.start()动态传入动画配置,每次触发动画时都会读取当前相机和控制点的实时状态作为起始值
  • 新增OrbitControls的ref,用来获取用户手动操作后的实时控制点坐标,修复原代码读取不存在的camera.lookAt属性的问题
  • 去掉了对_value内部属性的访问,改用官方提供的get()方法获取弹簧当前值,避免后续react-spring版本更新导致代码失效
  • useFrame里同步更新controls的target,避免动画结束后OrbitControls出现位置跳变

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 12:06:04