Last active
February 23, 2025 18:32
-
-
Save mobinni/407b58b88d0da0b416df4ce290ac58c5 to your computer and use it in GitHub Desktop.
React Native FPS monitor with reanimated
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useEffect } from 'react'; | |
import { TextInput } from 'react-native'; | |
import { PanGestureHandler } from 'react-native-gesture-handler'; | |
import Animated, { | |
useAnimatedGestureHandler, | |
useAnimatedProps, | |
useAnimatedStyle, | |
useFrameCallback, | |
useSharedValue, | |
} from 'react-native-reanimated'; | |
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput); | |
export const FPSMonitor = () => { | |
const fps = useSharedValue(0); | |
const lastTimestamp = useSharedValue(0); | |
const framesCount = useSharedValue(0); | |
const totalCount = useSharedValue(0); | |
const jsFps = useSharedValue(0); | |
const positionX = useSharedValue(0); | |
const positionY = useSharedValue(0); | |
const animatedRootStyles = useAnimatedStyle(() => { | |
return { | |
transform: [ | |
{ translateX: positionX.value }, | |
{ translateY: positionY.value }, | |
], | |
}; | |
}); | |
const _onPanHandlerStateChange = useAnimatedGestureHandler({ | |
onStart: (_, ctx: Record<string, number>) => { | |
ctx.startX = positionX.value; | |
ctx.startY = positionY.value; | |
}, | |
onActive: (event, ctx: Record<string, number>) => { | |
positionX.value = ctx.startX + event.translationX; | |
positionY.value = ctx.startY + event.translationY; | |
}, | |
}); | |
// UI thread | |
useFrameCallback((frameInfo) => { | |
if (lastTimestamp.value === 0) { | |
lastTimestamp.value = frameInfo.timestamp; | |
return; | |
} | |
framesCount.value += 1; | |
const elapsed = frameInfo.timestamp - lastTimestamp.value; | |
if (elapsed >= 1000) { | |
totalCount.value += 1; | |
const newFPS = Math.min((framesCount.value * 1000) / elapsed, 60); | |
fps.value = Math.round(newFPS); | |
lastTimestamp.value = frameInfo.timestamp; | |
framesCount.value = 0; | |
} | |
}); | |
// Measure JS Thread FPS | |
useEffect(() => { | |
let frameCount = 0; | |
let start = Date.now(); | |
let rafId: number; | |
const updateFps = () => { | |
'worklet'; | |
frameCount += 1; | |
const now = Date.now(); | |
const elapsed = now - start; | |
if (elapsed >= 1000) { | |
jsFps.value = Math.min(Math.round((frameCount * 1000) / elapsed), 60); | |
start = now; | |
frameCount = 0; | |
} | |
rafId = requestAnimationFrame(updateFps); | |
}; | |
rafId = requestAnimationFrame(updateFps); | |
return () => cancelAnimationFrame(rafId); | |
// eslint-disable-next-line react-hooks/exhaustive-deps | |
}, []); | |
const animatedFPS = useAnimatedProps(() => { | |
return { | |
text: `${fps.value}`, | |
defaultValue: `${fps.value}`, | |
}; | |
}); | |
const animatedJsFPS = useAnimatedProps(() => { | |
return { | |
text: `${jsFps.value}`, | |
defaultValue: `${jsFps.value}`, | |
}; | |
}); | |
return ( | |
<PanGestureHandler onHandlerStateChange={_onPanHandlerStateChange}> | |
<Animated.View | |
style={[ | |
animatedRootStyles, | |
{ | |
position: 'absolute', | |
top: 20, | |
right: 20, | |
width: 75, | |
backgroundColor: 'black', | |
padding: 8, | |
borderRadius: 4, | |
}, | |
]} | |
> | |
<Animated.View style={{ flexDirection: 'row', flex: 1 }}> | |
<Animated.Text | |
style={{ color: 'white', fontSize: 14, paddingRight: 8 }} | |
> | |
UI: | |
</Animated.Text> | |
<AnimatedTextInput | |
style={{ color: 'white', fontSize: 14, width: '100%' }} | |
animatedProps={animatedFPS} | |
/> | |
</Animated.View> | |
<Animated.View style={{ flexDirection: 'row', flex: 1 }}> | |
<Animated.Text | |
style={{ color: 'white', fontSize: 14, paddingRight: 8 }} | |
> | |
JS: | |
</Animated.Text> | |
<AnimatedTextInput | |
style={{ color: 'white', fontSize: 14, width: '100%' }} | |
animatedProps={animatedJsFPS} | |
/> | |
</Animated.View> | |
</Animated.View> | |
</PanGestureHandler> | |
); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment