Before we start, a word:
I started working on an AI native Boilerplate for Mobile Development. A solution that has a scalable and clean architecture, with the best approaches to have a great app performance. Adding more AI context and rules, to make the AI hallucinate less, and keep the clean code. You can use it either with Cursor, Antigravity, Claude Code,… and it gives the amazing results as expected. Also, it has most important features that help you launch your app fast
I m now in the Beta version, where i need people to test it, and gather feedback, you will get access to the code base for free, and i m looking for feedbacks.
Check it here: https://aimobilelauncher.com/
Also, I m working on a newsletter for Tech and non tech people, on how to use AI tools for App development: https://aimeetcode.substack.com/. Subscribe here: https://aimeetcode.substack.com/
If you need custom solution, or a mobile development, contact us in :https://casainnov.com/
Introduction
Smooth animations aren't a "nice-to-have." When users say "this app feels slow," they almost always mean animations are dropping frames - even if they'd never describe it that way.
After shipping features on apps with millions of active users, I've learned that animation performance is one of the first things that breaks at scale and one of the hardest to debug if you haven't seen the failure modes before. Here's what actually holds up in production.
Speaking of scale: in Mobile apps, I mean by scale is a bigger team, a lot of users who each person have a different devices, operating system, slow and old devices, or different sizes. In this part, you need to architect your app way better before
TL;DR
60 FPS means keeping animations off the JS thread
Use shared values, not React state
Animate transform + opacity; avoid layout properties
Batch animations and keep logic simple
Test on real devices, not simulators
Why it matters
Dropped frames make an app feel slow. Janky gestures make it feel cheap. And inconsistent motion is hard to explain to a product manager but immediately obvious to users. Beyond UX, smoother animations also mean better battery life, better behavior on low-end devices, and less competition with your business logic.
The one rule that matters most
If your animation depends on the JS thread, you've already lost.
Reanimated runs animations on the UI thread, independent from JS. Everything else in this article is just ways to not break that.
1. Animate only what the native driver supports
Safe: transform, opacity. Slow: width, height, backgroundColor, anything layout-driven.
`
// Good
const style = useAnimatedStyle(() => ({
opacity: opacity.value,
transform: [{ translateY: y.value }, { scale: scale.value }],
}));
// Bad
const style = useAnimatedStyle(() => ({
width: width.value,
height: height.value,
}));`
If you feel like you need to animate the layout, stop and rethink the design. The animation is probably covering for a structural problem.
2. One shared value, not five
I still see this in senior-level code:
// Too many values
const opacity = useSharedValue(0);
const scale = useSharedValue(0);
const translateY = useSharedValue(0);
Collapse them:
const progress = useSharedValue(0);
const style = useAnimatedStyle(() => ({
opacity: progress.value,
transform: [
{ scale: progress.value },
{ translateY: (1 - progress.value) * 20 },
],
}));
One value, fewer calculations, smoother frames. The math is trivial on the UI thread.
3. Batch your animations
Starting animations at different times means the thread is doing more work than it needs to. A single withTiming can drive opacity, scale, and translation at once:
useEffect(() => {
progress.value = withTiming(1, { duration: 300 });
}, []);
Obvious in hindsight, but easy to miss when you're wiring up components quickly.
4. Springs for gestures
Timing functions work fine for entrances and exits. For gestures, springs are better - they feel more physical, and they put less pressure on the frame budget:
translateX.value = withSpring(0, {
damping: 15,
stiffness: 150,
});
This matters most for drag, swipe, and scroll interactions, where any stiffness or lag is immediately noticeable.
5. Keep gesture handlers dumb
No conditionals. No calculations. No JS calls inside gesture handlers.
onActive: (event) => {
x.value = event.translationX;
y.value = event.translationY;
}
The UI thread moves pixel. Anything else belongs else where.
6. Clean up animations
Animations that outlive their components create memory pressure. Easy to overlook, adds up on apps with deep navigation:
useEffect(() => {
return () => {
progress.value = 0;
};
}, []);
7. How to test
Simulators will lie to you. A simulator doesn't have thermal throttling, doesn't run on 3GB of RAM, and doesn't have fifteen background apps fighting for CPU. Test on low-end Android, older iPhones, and devices that have been warm for a while.
If it holds up there, you're fine.
Always go with an old device,
What I actually see in code reviews
Animating every component entry - motion should mean something; overuse kills that
Animations tied to React state - state updates and animation updates are not the same thing
Durations over 500ms - they feel slow and block frames; tighten them
Elaborate easing curves - usually not worth it; keep it consistent
Before you ship
Ask yourself: Is this running on the UI thread? Can one shared value drive it? Did I test it on real hardware? If yes across the board, you're done.
FAQ
Does Reanimated always guarantee 60 FPS?
No. It gives you the right architecture. You still have to use it correctly.
Should I use Reanimated for every animation?
Not necessarily. Simple layout animations are fine with LayoutAnimation. Reanimated earns its complexity with gestures and anything UI-thread-sensitive.
Are springs always better than timing?
For interactions, yes. For entrances and exits, timing is usually fine and easier to control.
Is performance worse with Expo?
No. Expo and Reanimated together are production-proven

Top comments (0)