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

在React Three Fiber+use-cannon应用中如何实现点击平滑旋转3D物体?

点击平滑旋转实现方案

你当前直接调用api.rotation.set是瞬时修改物理体的旋转属性,没有过渡过程,所以不会有平滑动画效果。因为使用了use-cannon物理引擎,实现动画时必须同步更新物理体的旋转属性,否则会出现渲染效果和物理碰撞判定不一致的问题。以下是两种可直接落地的实现方案:

方案一:使用@react-spring/three实现(生态推荐)

这个是R3F生态最常用的动画方案,支持弹性、匀速等多种动画效果,适配性好,改动量小。

  1. 先安装依赖:
npm install @react-spring/three
  1. 修改后的组件代码:
import { useSpring, animated } from '@react-spring/three'
import { useFrame } from '@react-three/fiber'
import { useBox } from '@cannon/react-cannon'

export function HomeLink() {
  const [ref, api] = useBox(() => ({ mass: 1, position: [0, 0, 0], args: [6, 2, 2] }))
  // 定义弹簧动画状态
  const [rotation, setRotation] = useSpring(() => ({
    rotation: [0, 0, 0],
    // 调整参数控制动画效果:tension越大动画越快、friction越大晃动越少、mass越大动画越平缓
    config: { mass: 2, tension: 220, friction: 18 }
  }))

  const handleClick = () => {
    // 点击时更新动画目标旋转值
    const currentZ = rotation.rotation.get()[2]
    setRotation({ rotation: [0, 0, currentZ + Math.PI / 2] })
  }

  // 每一帧同步动画值到物理引擎
  useFrame(() => {
    api.rotation.set(...rotation.rotation.get())
  })

  return(
    {/* 用animated.mesh替换普通mesh,绑定动画属性 */}
    <animated.mesh 
      ref={ref} 
      scale={[6, 2, 2]} 
      onClick={handleClick}
      rotation={rotation.rotation}
    >
      <boxBufferGeometry attach="geometry"/>
      <meshBasicMaterial attach="material" color="red"/>
    </animated.mesh>
  )
};

如果不需要弹性效果,想要匀速旋转,可以把spring的config替换为:

config: { duration: 300, easing: t => t }

调整duration的数值即可修改动画总时长,单位为毫秒。

方案二:无额外依赖的插值实现

如果不想引入新的依赖,可以直接用Three.js内置的插值工具配合useFrame实现平滑过渡:

import { useRef } from 'react'
import { useFrame } from '@react-three/fiber'
import { useBox } from '@cannon/react-cannon'
import * as THREE from 'three'

export function HomeLink() {
  const [ref, api] = useBox(() => ({ mass: 1, position: [0, 0, 0], args: [6, 2, 2] }))
  // 存储当前旋转值和目标旋转值
  const currentRotZ = useRef(0)
  const targetRotZ = useRef(0)
  // 过渡系数,取值范围0-1,数值越大动画速度越快
  const lerpFactor = 0.12

  const handleClick = () => {
    targetRotZ.current += Math.PI / 2
  }

  useFrame(() => {
    // 插值计算当前帧的旋转值
    currentRotZ.current = THREE.MathUtils.lerp(currentRotZ.current, targetRotZ.current, lerpFactor)
    // 同步到物理引擎
    api.rotation.set(0, 0, currentRotZ.current)
  })

  return(
    <mesh 
      ref={ref} 
      scale={[6, 2, 2]} 
      onClick={handleClick}
    >
      <boxBufferGeometry attach="geometry"/>
      <meshBasicMaterial attach="material" color="red"/>
    </mesh>
  )
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.12 16:12:02