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

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!"));
    

问题根源

  1. 定时器与动画不同步:setTimeout的计时精度依赖浏览器定时器,而Framer Motion动画基于requestAnimationFrame,两者的时间计算存在偏差,导致状态更新延迟。
  2. 精确判断value === 0不可行:动画过程中count是渐变的浮点值,几乎不会精确等于0,所以onUpdate里的判断根本触发不了。
  3. 计数跳变不自然:直接用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 23:38:11