Animations in Mobile Applications: Lottie, Rive, Spring and Reanimated
Animation either runs at 60/120 fps and feels natural, or it shows in Xcode Instruments as red stripes on the main thread. There is no middle ground.
Why UIView.animate Breaks on Complex Scenarios
UIView.animate(withDuration:) and ObjectAnimator on Android are the right choice for simple transitions. But as soon as animation becomes interactive (user drags an element, speed depends on gesture), you need a different approach.
On iOS for gesture-driven animation, the right tool is UIViewPropertyAnimator. It allows pausing, reversing, and modifying the animation in progress. Typical case: bottom sheet that follows a finger, continues moving with inertia after release, and snaps to the nearest position. With UIView.animate this either doesn't work at all or requires manual physics.
In SwiftUI, withAnimation works out of the box, but interactivity is limited — there's no direct UIViewPropertyAnimator equivalent. Workaround: .gesture(DragGesture()) + @GestureState + explicit position calculation. Or we use SwiftUI Animations API with Animation.spring(duration:bounce:) from iOS 17.
React Native Reanimated: Worklets on UI Thread
React Native Animated API runs animations in JS thread — this is a source of jank when the bridge is busy. Reanimated 3 solves this problem through worklets: functions that compile and execute directly on the UI thread without crossing the JS bridge.
Example: parallax scroll header. On basic Animated.Value with fast scrolling, FPS drops to 40-45 on mid-range Android. On Reanimated with useAnimatedScrollHandler — stable 60 fps, because all position recalculation happens on the UI thread.
Reanimated 3 with useSharedValue, useAnimatedStyle and withSpring/withTiming — this is the current standard for animations in React Native. Gesture Handler v2 is tightly integrated: useAnimatedGestureHandler replaces PanResponder and also runs on the UI thread.
Lottie vs Rive
Both tools solve "designer makes animation, developer adds file." But in fundamentally different ways.
Lottie exports After Effects animation to JSON. Rendering is vector, supports iOS (lottie-ios), Android (lottie-android), Flutter (lottie), React Native (lottie-react-native). Limitations: no interactivity (animation is linear), large JSON files with particles and blur effects render heavily. Blur through Lottie on Android is a guaranteed FPS drop.
Rive is a state machine. Animation has states and transitions between them, controlled from code via StateMachineInput. Button with hover, pressed, loading, success states — this is one Rive animation with four states, not four separate files. Rendering is hardware via Metal/OpenGL. File sizes are smaller than Lottie due to binary .riv format.
Choice is simple: static decorative animation (splash screen, onboarding illustrations) — Lottie. Interactive UI elements with states — Rive.
Spring Physics and Hero Transitions
Spring animation feels natural because it mimics physics — mass, stiffness, and damping. In SwiftUI: Animation.spring(response:dampingFraction:). In Android Compose: spring(dampingRatio = Spring.DampingRatioMediumBouncy).
For Hero transitions (element "flies" between screens), on iOS use UIViewControllerTransitioningDelegate + UIViewControllerAnimatedTransitioning. In SwiftUI with iOS 17 — matchedTransitionSource + navigationTransition(.zoom). On Flutter — Hero widget that works out of the box.
Typical mistake in Hero transitions: animation starts normally but on the target screen the element "jumps" to the final position. Reason — AutoLayout constraints apply before animation completes. Solution: layoutIfNeeded() in the animation block or using transform instead of frame changes.
Timeline for animation layer: basic screen transitions and microinteractions — 1 week. Lottie/Rive integration with design system — 3-5 days after receiving final files. Custom gesture-driven interactivity (sheet, drawer, carousel with physics) — 1-2 weeks.







