React中Framer Motion倒计时动画不精确,状态更新延迟问题
Framer Motion倒计时状态更新延迟问题
我在React应用中用Framer Motion做5秒倒计时,但倒计时走到0秒后,屏幕上的状态文本才会更新,而且计数变化看起来很不自然。我的实现代码如下:
const [status, setStatus] = useState("Waiting..."); const count = useMotionValue(5); const time = useTransform(count, Math.round); useEffect(() => { const animation = animate(count, 0, { duration: 5 }); setTimeout(() => { setStatus("Finished!"); }, 5 * 1000); return animation.stop; }, []);
我试过两种方法修复,但问题依旧:Finished!文本还是会延迟显示,计数跳变也不顺畅:
- 用
animate的onUpdate方法:const animation = animate(count, 0, { duration: 5, onUpdate: (value) => { if (value === 0) setStatus("Finished!"); }, }); - 用动画的
.then回调:const animation = animate(count, 0, { duration: 5 }).then(() => setStatus("Finished!"));
问题根源
- 定时器与动画不同步:
setTimeout的计时精度依赖浏览器定时器,而Framer Motion动画基于requestAnimationFrame,两者的时间计算存在偏差,导致状态更新延迟。 - 精确判断
value === 0不可行:动画过程中count是渐变的浮点值,几乎不会精确等于0,所以onUpdate里的判断根本触发不了。 - 计数跳变不自然:直接用
Math.round转换运动值,会导致数字在非整秒时跳转,比如5可能还没到1秒就变成4了,不符合倒计时的直观感受。
修复方案
方法1:用onComplete回调(官方推荐)
直接用Framer Motion动画自带的onComplete回调,这是专门监听动画结束的钩子,能保证和动画完全同步:
import { useState, useEffect } from "react"; import { useMotionValue, useTransform, animate } from "framer-motion"; export default function Countdown() { const [status, setStatus] = useState("Waiting..."); const count = useMotionValue(5); // 用Math.floor让倒计时向下取整,符合常规倒计时逻辑 const time = useTransform(count, (val) => Math.floor(val)); useEffect(() => { const animation = animate(count, 0, { duration: 5, ease: "linear", // 线性动画,保证倒计时匀速变化 onComplete: () => { setStatus("Finished!"); }, }); return () => animation.stop(); }, []); return ( <div> <h1>{time.get()}</h1> <p>{status}</p> </div> ); }
方法2:监听运动值变化
如果需要更灵活的触发时机,可以监听count的变化,用阈值判断是否接近0:
import { useState, useEffect } from "react"; import { useMotionValue, useTransform, animate } from "framer-motion"; export default function Countdown() { const [status, setStatus] = useState("Waiting..."); const count = useMotionValue(5); const time = useTransform(count, (val) => Math.floor(val)); useEffect(() => { const animation = animate(count, 0, { duration: 5, ease: "linear", }); // 监听运动值变化,当小于等于0.1时更新状态 const unsubscribe = count.onChange((latest) => { if (latest <= 0.1) { setStatus("Finished!"); unsubscribe(); // 触发后取消监听,避免重复执行 } }); return () => { animation.stop(); unsubscribe(); }; }, []); return ( <div> <h1>{time.get()}</h1> <p>{status}</p> </div> ); }
关键优化点
- 加
ease: "linear":让倒计时匀速变化,避免默认缓动效果导致的快慢不均; - 用
Math.floor替换Math.round:让数字在整秒时跳转,符合用户对倒计时的预期; - 避免精确判断
value === 0:用阈值(比如0.1)代替,确保状态能及时触发更新。
内容的提问来源于stack exchange,提问作者Juan Fragoso
相关产品推荐
相关产品推荐

