在React Three Fiber+use-cannon应用中如何实现点击平滑旋转3D物体?
点击平滑旋转实现方案
你当前直接调用api.rotation.set是瞬时修改物理体的旋转属性,没有过渡过程,所以不会有平滑动画效果。因为使用了use-cannon物理引擎,实现动画时必须同步更新物理体的旋转属性,否则会出现渲染效果和物理碰撞判定不一致的问题。以下是两种可直接落地的实现方案:
方案一:使用@react-spring/three实现(生态推荐)
这个是R3F生态最常用的动画方案,支持弹性、匀速等多种动画效果,适配性好,改动量小。
- 先安装依赖:
npm install @react-spring/three
- 修改后的组件代码:
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
相关产品推荐
相关产品推荐

