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

React Native Reanimated SVG环形进度条初始动画失效,更新动画正常

React Native Reanimated环形进度组件初始动画问题解决

问题描述

使用Reanimated + SVG实现环形进度组件时,后续修改progress属性能正常触发动画,但初始渲染时设置progress={40},环形会直接显示40%进度,无法看到从0%到40%的过渡动画。尝试过在useEffect、useAnimatedReaction、onLayout中更新共享值,均无法触发初始动画。

原因分析

  1. 初始渲染时size值为0,直到onLayout触发后才会获取到容器尺寸,此时直接设置progressVal为目标值,没有应用动画过渡
  2. 未区分初始渲染和后续更新的场景,缺少仅执行一次的初始动画触发逻辑
  3. 原代码中直接用progressVal.set(progress)赋值,未使用withTiming包裹,导致无动画跳变

解决方案(修改后的完整代码)

import { View } from "react-native";
import Animated, {
  useAnimatedProps,
  useAnimatedReaction,
  useDerivedValue,
  useSharedValue,
  withTiming,
} from "react-native-reanimated";
import Svg, { Circle } from "react-native-svg";

const AnimatedCircle = Animated.createAnimatedComponent(Circle);

export default function CircleProgress({ progress = 0 }: { progress: number }) {
  const size = useSharedValue(0);
  const radius = useDerivedValue(() => (size.value / 2) * 0.8); // 留出发描边宽度的空间
  const circumference = useDerivedValue(() => 2 * Math.PI * radius.value);
  const progressVal = useSharedValue(0);
  // 标记是否已执行过初始动画
  const isInitialized = useSharedValue(false);

  const offset = useDerivedValue(
    () => circumference.value * (1 - progressVal.value / 100),
  );

  // 监听size变化,触发初始动画
  useAnimatedReaction(
    () => size.value,
    (currentSize) => {
      // 仅当获取到有效尺寸且未初始化时,执行初始动画
      if (currentSize > 0 && !isInitialized.value) {
        progressVal.value = withTiming(progress, { duration: 800 });
        isInitialized.value = true;
      }
    },
  );

  // 监听progress props变化,处理后续更新动画
  useAnimatedReaction(
    () => progress,
    (currentProgress) => {
      // 已初始化后,后续修改progress直接触发动画
      if (isInitialized.value) {
        progressVal.value = withTiming(currentProgress, { duration: 800 });
      }
    },
  );

  const barProps = useAnimatedProps(() => {
    return {
      strokeDashoffset: offset.value,
      strokeDasharray: circumference.value,
      cx: size.value / 2,
      cy: size.value / 2,
      strokeWidth: size.value * 0.05,
      r: radius.value,
      originX: size.value / 2,
      originY: size.value / 2,
      rotation: -90,
    };
  });

  const trackProps = useAnimatedProps(() => ({
    cx: size.value / 2,
    cy: size.value / 2,
    strokeWidth: size.value * 0.05,
    r: radius.value,
  }));

  return (
    <View
      className="w-full aspect-square bg-amber-300"
      onLayout={(e) => {
        size.set(e.nativeEvent.layout.width);
      }}
    >
      <Svg width="100%" height="100%">
        {/* 背景轨道 */}
        <AnimatedCircle
          stroke="#e5e5e5"
          fill="none"
          animatedProps={trackProps}
        />
        {/* 进度圆弧 */}
        <AnimatedCircle
          stroke="blue"
          fill="none"
          strokeLinecap="round"
          animatedProps={barProps}
        />
      </Svg>
    </View>
  );
}

关键改动说明

  • 新增isInitialized共享值,确保初始动画仅执行一次,避免重复触发
  • 拆分useAnimatedReaction逻辑:
    • 第一个reaction监听size变化,在首次获取到有效容器尺寸时,用withTiming将progressVal从0动画过渡到目标进度
    • 第二个reaction监听progress props变化,后续修改进度时直接触发动画
  • 移除barProps中strokeDashoffset的withTiming包裹,因为动画逻辑已经放在progressVal的赋值中,避免重复应用动画导致冲突
  • 调整动画触发时机,确保在容器尺寸确定后才执行初始动画,避免因circumference未计算完成导致的动画异常

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 21:34:56