React Native Reanimated SVG环形进度条初始动画失效,更新动画正常
React Native Reanimated环形进度组件初始动画问题解决
问题描述
使用Reanimated + SVG实现环形进度组件时,后续修改progress属性能正常触发动画,但初始渲染时设置progress={40},环形会直接显示40%进度,无法看到从0%到40%的过渡动画。尝试过在useEffect、useAnimatedReaction、onLayout中更新共享值,均无法触发初始动画。
原因分析
- 初始渲染时
size值为0,直到onLayout触发后才会获取到容器尺寸,此时直接设置progressVal为目标值,没有应用动画过渡 - 未区分初始渲染和后续更新的场景,缺少仅执行一次的初始动画触发逻辑
- 原代码中直接用
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监听
progressprops变化,后续修改进度时直接触发动画
- 第一个reaction监听
- 移除
barProps中strokeDashoffset的withTiming包裹,因为动画逻辑已经放在progressVal的赋值中,避免重复应用动画导致冲突 - 调整动画触发时机,确保在容器尺寸确定后才执行初始动画,避免因
circumference未计算完成导致的动画异常
内容的提问来源于stack exchange,提问作者yathavan
相关产品推荐
相关产品推荐

