react-native-reanimated
react-native-reanimated copied to clipboard
Animating many views with worklets is significantly slower than with nodes
Description
I'm trying to animate 200 views using Reanimated 2.10.0 and I noticed that the animation is not smooth when I'm implementing it with worklets while it never drops a frame when I'm implementing it as node-based animation. The two implementations are pretty similar, with a single time
animated value that drives the animation for all the animated views.
Here are two screenshots of the iOS simulator with React Native's perf monitor:
Worklet (~42fps) | Node (~60fps) |
---|---|
![]() |
![]() |
I ran the same code on a low-end Android phone in release mode with fewers views and I noticed the same performance issue. I understand that running worklets in their own JS VM is obviously slower than evaluating the result of a few animated nodes but I would not have expected such an impact on the performances. For now, I can fallback to the animated node version but I'm mostly worried that there will be no alternatives if Reanimated 3 drops support for animated nodes.
Steps to reproduce
Here is the code for the worklet animation:
import React, {useEffect, useRef} from 'react';
import {View} from 'react-native';
import Animated, {
Easing,
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated';
export const ConfettiLayerWorklet = () => {
const time = useSharedValue(0);
useEffect(() => {
time.value = withTiming(10000000, {
duration: 10000000,
easing: Easing.linear,
});
});
return (
<View style={{flex: 1}}>
{Array.from({length: 200}, (_, index) => (
<Confetti key={index} time={time} />
))}
</View>
);
};
const Confetti = ({time}) => {
const startX = useRef(random(0, 375)).current;
const startY = useRef(random(0, 812)).current;
const velocityX = useRef(random(0, 50) / 1000).current;
const velocityY = useRef(random(70, 150) / 1000).current;
const animatedStyle = useAnimatedStyle(() => ({
transform: [
{translateX: (startX + time.value * velocityX) % 375},
{translateY: (startY + time.value * velocityY) % 812},
],
}));
return (
<Animated.View
style={[
{
position: 'absolute',
width: 20,
height: 20,
backgroundColor: 'blue',
},
animatedStyle,
]}
/>
);
};
function random(min, max) {
return min + Math.random() * (max - min);
}
And its counterpart using animated nodes:
import React, {useEffect, useRef} from 'react';
import {View} from 'react-native';
import Animated, {
add,
EasingNode,
modulo,
multiply,
timing,
} from 'react-native-reanimated';
export const ConfettiLayerNode = () => {
const time = useRef(new Animated.Value(0)).current;
useEffect(() => {
const animation = timing(time, {
toValue: 10000000,
duration: 10000000,
easing: EasingNode.linear,
});
animation.start();
return () => animation.stop();
});
return (
<View style={{flex: 1}}>
{Array.from({length: 200}, (_, index) => (
<Confetti key={index} time={time} />
))}
</View>
);
};
const Confetti = ({time}) => {
const startX = useRef(random(0, 375)).current;
const startY = useRef(random(0, 812)).current;
const velocityX = useRef(random(0, 50) / 1000).current;
const velocityY = useRef(random(70, 150) / 1000).current;
const animatedStyle = {
transform: [
{
translateX: modulo(add(startX, multiply(time, velocityX)), 375),
},
{translateY: modulo(add(startY, multiply(time, velocityY)), 812)},
],
};
return (
<Animated.View
style={[
{
position: 'absolute',
width: 20,
height: 20,
backgroundColor: 'blue',
},
animatedStyle,
]}
/>
);
};
function random(min, max) {
return min + Math.random() * (max - min);
}
Snack or a link to a repository
https://github.com/simontreny/reanimated-perf-issue
Reanimated version
2.10.0
React Native version
0.69.5
Platforms
iOS
JavaScript runtime
JSC
Workflow
React Native (without Expo)
Architecture
Paper (Old Architecture)
Build type
Debug mode
Device
iOS simulator
Device model
Samsung Galaxy A5
Acknowledgements
Yes