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

使用useAnimate钩子报错:No valid element provided 求助

解决Framer Motion useAnimate在React Three Fiber中抛出"No valid element provided"错误

问题原因

useAnimate 钩子默认是为DOM元素设计的,而你在React Three Fiber(R3F)中使用的 <motion.group> 本质是Three.js的3D对象实例,并非DOM节点,直接将其ref传给useAnimate的scope会导致钩子无法识别有效元素,从而抛出错误。

虽然<motion.group>的内置动画(比如whileHover)能正常工作,是因为framer-motion-3d对R3F组件做了底层适配,但useAnimate需要依赖特定的上下文(比如MotionCanvas提供的3D动画上下文)才能正确处理Three.js对象。

可行解决方案

方案1:正确配置MotionCanvas并修复异常

如果你坚持使用useAnimate,必须确保你的R3F画布是MotionCanvas,并排查之前出现的异常:

  • 检查framer-motion-3d和react-three-fiber的版本兼容性,确保两者版本匹配(比如framer-motion-3d@>=10.x对应react-three-fiber@>=8.x)
  • 确保MotionCanvas包裹所有需要动画的3D组件,包括你的PlayerCard
  • 移除常规的<Canvas>,替换为<MotionCanvas>,示例:
import { MotionCanvas } from 'framer-motion-3d'

function App() {
  return (
    <MotionCanvas shadows camera={{ position: [0, 0, 5] }}>
      {/* 你的场景内容,包括PlayerCard */}
      <PlayerCard />
    </MotionCanvas>
  )
}

完成上述配置后,useAnimate应该能正确识别motion.group的ref。

方案2:改用framer-motion-3d的useAnimation钩子

如果MotionCanvas的异常暂时无法解决,可以使用专门为3D场景设计的useAnimation钩子替代useAnimate,它能直接和R3F的motion组件配合:

import { motion } from "framer-motion-3d"
import { useAnimation } from "framer-motion-3d" // 注意从framer-motion-3d导入

export const PlayerCard = ({ imageUrl, position, cardIndex, cardShape, cardBody }: PlayergCardProps) => {
  const playerCardState = useRecoilValue(playerCardAtom);
  const playerCardActions = usePlayerCardActions();
  const controls = useAnimation() // 替换useAnimate为useAnimation

  useEffect(() => {
    if (playerCardState.isPicked === cardIndex) {
      controls.start({ 
        z: 0.5,
        transition: { 
          repeat: Infinity,
          repeatType: "mirror",
          ease: "easeInOut",
          duration: 1
        }
      })
    } else {
      controls.start({ z: 0 })
    }
  }, [playerCardState.isPicked, controls])

  function handleClick() {
    playerCardActions.pickCard(cardIndex)
  }  

  return (
    <motion.group 
      animate={controls} // 将controls传给animate prop
      whileHover={{scale: 0.5}} 
      position={[position.x,position.y,position.z]} 
      scale={[0.45, 0.5, 0.5]} 
      onClick={handleClick}
    >
      {/* Card body */}
      <motion.mesh position={[0, 0, 0.1]} castShadow>
        <motion.bufferGeometry attach="geometry" {...cardBody} />
        <motion.meshBasicMaterial attach="material" color="#51CF66" />
        <motion.meshPhongMaterial attach="material" color="#51CF66" />
      </motion.mesh>
    </motion.group>
  )
}

方案3:直接使用motion组件的animate prop

对于状态驱动的动画,也可以直接通过状态控制animate prop,无需额外钩子:

export const PlayerCard = ({ imageUrl, position, cardIndex, cardShape, cardBody }: PlayergCardProps) => {
  const playerCardState = useRecoilValue(playerCardAtom);
  const playerCardActions = usePlayerCardActions();
  const isPicked = playerCardState.isPicked === cardIndex

  function handleClick() {
    playerCardActions.pickCard(cardIndex)
  }  

  return (
    <motion.group 
      animate={{ 
        z: isPicked ? 0.5 : 0,
        transition: isPicked ? { 
          repeat: Infinity,
          repeatType: "mirror",
          ease: "easeInOut",
          duration: 1
        } : {}
      }}
      whileHover={{scale: 0.5}} 
      position={[position.x,position.y,position.z]} 
      scale={[0.45, 0.5, 0.5]} 
      onClick={handleClick}
    >
      {/* Card body */}
      <motion.mesh position={[0, 0, 0.1]} castShadow>
        <motion.bufferGeometry attach="geometry" {...cardBody} />
        <motion.meshBasicMaterial attach="material" color="#51CF66" />
        <motion.meshPhongMaterial attach="material" color="#51CF66" />
      </motion.mesh>
    </motion.group>
  )
}

内容的提问来源于stack exchange,提问作者Lukas Luke Stateczny

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 15:40:40