Плавная анимация это 60 кадров в секунду, а на экранах 120 Гц ещё быстрее. Главный вопрос анимаций в RN: на каком потоке считается каждый кадр. Если на JS-потоке, любая работа React (рендер списка, разбор JSON) даст рывок.
Animated
Встроенный API:
const opacity = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(opacity, {
toValue: 1,
duration: 300,
useNativeDriver: true,
}).start();
}, []);
<Animated.View style={{ opacity }} />
Animated.timing,spring,decay; композицияsequence,parallel,stagger,loop.interpolateпереводит значение в другой диапазон:opacity.interpolate({ inputRange: [0, 1], outputRange: ['0deg', '90deg'] }).
useNativeDriver: true отправляет описание анимации на нативную сторону один раз, и дальше кадры считаются без JS. Ограничение: только свойства, не влияющие на раскладку, то есть transform и opacity. width, height, top, цвета фона нативный драйвер не поддерживает, такие анимации идут через JS.
Reanimated
Стандарт для сложных анимаций и жестов. Ключевые понятия:
- Shared value (
useSharedValue): значение, доступное и JS, и UI-потоку. Меняется через.value. - Worklet: маленькая функция, которая выполняется на UI-потоке. Колбэки
useAnimatedStyle, обработчики жестов иwithTimingработают как worklets. useAnimatedStyle: стиль, пересчитываемый на UI-потоке при изменении shared value, без перерисовки компонента React.
const offset = useSharedValue(0);
const style = useAnimatedStyle(() => ({
transform: [{ translateX: offset.value }],
}));
const onPress = () => {
offset.value = withSpring(offset.value + 100);
};
- Из worklet вызвать обычную JS-функцию напрямую нельзя: нужен
runOnJS(fn)(args)(или его аналоги в новых версиях). - Изменение shared value не вызывает рендер React. Если значение нужно показать в обычном
Text, его переносят в состояние черезuseAnimatedReactionиrunOnJS. - Анимировать можно и свойства раскладки (
width,height), так как вычисление идёт на UI-потоке. - Layout animations (
entering,exiting,layout) анимируют появление, удаление и перестановку элементов.
Reanimated 4 требует новую архитектуру, вынес worklets в отдельный пакет react-native-worklets и добавил анимации в стиле CSS (переходы и ключевые кадры прямо в стилях).
Жесты
Встроенная система ответчиков (PanResponder) работает на JS-потоке. react-native-gesture-handler распознаёт жесты нативно и вместе с Reanimated даёт жесты, которые следуют за пальцем без участия JS:
const x = useSharedValue(0);
const pan = Gesture.Pan()
.onChange((e) => { x.value += e.changeX; })
.onEnd(() => { x.value = withSpring(0); });
const style = useAnimatedStyle(() => ({ transform: [{ translateX: x.value }] }));
<GestureDetector gesture={pan}>
<Animated.View style={style} />
</GestureDetector>
Жесты комбинируются: Gesture.Simultaneous (одновременно, например масштаб и поворот), Gesture.Exclusive (один из, например двойной тап важнее одиночного), Gesture.Race. Корень приложения оборачивают в GestureHandlerRootView.
Как выбрать
| Задача | Инструмент |
|---|---|
| Появление, прозрачность, сдвиг | Animated с native driver или Reanimated |
| Анимация, связанная с жестом | Gesture Handler + Reanimated |
| Анимация раскладки списка | Reanimated layout animations, LayoutAnimation |
| Сложная иллюстрация от дизайнера | Lottie, Rive |
Частые ошибки
- Анимировать
widthсuseNativeDriver: true: ошибка. setStateна каждом кадре анимации.- Читать
sharedValue.valueв рендере и ждать перерисовки. - Вызывать JS-функцию из worklet без
runOnJS.
Что спросят на собесе
- Что такое native driver и его ограничения.
- Как устроен Reanimated: shared values, worklets, UI-поток.
- Почему анимация на JS-потоке дёргается при рендере.
- Как сделать перетаскивание, которое не тормозит.
- Как скомбинировать жесты.