はじめに
React Nativeでアプリを作っていると、アニメーションの実装で悩むことが多いんですよね。標準のAnimated APIでもそれなりに動くんですが、複雑な動きを実装しようとするとパフォーマンスが落ちたり、カクついたりする。
そこで登場するのがReact Native Reanimatedです。これ、正直なところ使い始めるまでは「また新しいライブラリか」と思っていたんですが、実際に触ってみると全然違う。ヌルヌル動く。
React Native Reanimatedは、Software Mansion社が開発したアニメーションライブラリで、GitHubスター数10,600以上、月間ダウンロード数900万回という実績があります。ShopifyやExpoもスポンサーについているので、継続性の面でも安心できますね。
特徴・メリット
UIスレッドで動作する
個人的にこれが一番大きいポイントだと思います。
通常のReact NativeアニメーションはJavaScriptスレッドで計算されるため、重い処理と競合するとフレーム落ちが発生します。React Native ReanimatedはUIスレッドで直接動作するため、120fps以上のスムーズなアニメーションが実現できる。
体感としては、ネイティブアプリと遜色ないレベルの動きが出せるようになります。
宣言的なAPI
アニメーションのコードって、命令的に書くと複雑になりがちなんですよね。React Native Reanimatedは宣言的なAPIを提供しているので、「こうなってほしい」という状態を記述するだけでいい。
const animatedStyle = useAnimatedStyle(() => ({
opacity: isVisible.value ? 1 : 0,
}));
これだけで透明度のアニメーションが動く。シンプルですね。
ジェスチャーとの統合
React Native Gesture Handlerと組み合わせることで、スワイプやピンチなどのジェスチャーに連動したアニメーションが実装できます。これが意外と重要で、ユーザー体験を大きく左右する部分なんですよ。
豊富な機能
- レイアウトアニメーション(要素の追加・削除時)
- センサー連携(ジャイロスコープ、加速度計)
- キーボード連携(キーボードの表示・非表示に反応)
- 画面遷移アニメーション
ほぼ何でもできると言っていいレベルです。
インストール方法
前提条件
React Native Reanimated 4.xを使う場合、New Architecture(Fabric)が必須です。旧アーキテクチャを使っている場合はバージョン3.xを使ってください。
Expoプロジェクトの場合
npm install react-native-reanimated react-native-worklets
インストール後、ネイティブ依存関係を再構築します。
npx expo prebuild
React Native CLIの場合
パッケージをインストールした後、babel.config.jsにプラグインを追加します。
module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: [
// 他のプラグイン...
'react-native-worklets/plugin', // 最後に追加
],
};
iOSの場合は追加でPodをインストールします。
cd ios && pod install && cd ..
基本的な使い方
SharedValueを理解する
React Native Reanimatedの中核となるのがSharedValueという概念です。これはUIスレッドとJavaScriptスレッドで共有される値で、変更するとアニメーションが自動的にトリガーされます。
import { useSharedValue } from 'react-native-reanimated';
function MyComponent() {
const opacity = useSharedValue(0);
// 値を変更するとアニメーションが動く
const handlePress = () => {
opacity.value = 1;
};
return (
// ...
);
}
useAnimatedStyleでスタイルを適用
SharedValueをスタイルに反映するにはuseAnimatedStyleを使います。
import Animated, {
useSharedValue,
useAnimatedStyle
} from 'react-native-reanimated';
function FadeInBox() {
const opacity = useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => ({
opacity: opacity.value,
}));
return (
<Animated.View style={[styles.box, animatedStyle]}>
<Text>Hello</Text>
</Animated.View>
);
}
ポイントはAnimated.Viewを使うことですね。通常のViewではなく、Reanimatedが提供するコンポーネントを使う必要があります。
withTimingでスムーズな変化を
値をいきなり変更すると瞬間的に切り替わってしまいます。withTimingを使うと、指定した時間をかけて滑らかに変化させることができます。
import { withTiming, Easing } from 'react-native-reanimated';
const handlePress = () => {
opacity.value = withTiming(1, {
duration: 500,
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
});
};
イージング関数も豊富に用意されていて、linear、ease、bounce、elasticなど、必要なものはだいたい揃っています。
withSpringで物理ベースのアニメーション
個人的にはwithSpringをよく使います。バネのような物理ベースのアニメーションで、自然な動きが出せるんですよね。
import { withSpring } from 'react-native-reanimated';
const handlePress = () => {
scale.value = withSpring(1.2, {
damping: 10,
stiffness: 100,
});
};
dampingが減衰、stiffnessがバネの固さです。値を調整することで、弾むような動きやしっとりした動きを表現できます。
実践的なユースケース
フェードイン・スライドインアニメーション
画面表示時に要素がフェードインしながらスライドしてくる、よくあるパターンです。
import React, { useEffect } from 'react';
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
withDelay,
} from 'react-native-reanimated';
function AnimatedCard({ index }: { index: number }) {
const opacity = useSharedValue(0);
const translateY = useSharedValue(20);
useEffect(() => {
const delay = index * 100; // 順番にアニメーション
opacity.value = withDelay(delay, withTiming(1, { duration: 400 }));
translateY.value = withDelay(delay, withTiming(0, { duration: 400 }));
}, []);
const animatedStyle = useAnimatedStyle(() => ({
opacity: opacity.value,
transform: [{ translateY: translateY.value }],
}));
return (
<Animated.View style={[styles.card, animatedStyle]}>
{/* カードの内容 */}
</Animated.View>
);
}
withDelayを使うことで、リスト内の要素を順番にアニメーションさせることができます。
タップで拡大縮小するボタン
ボタンを押したときに少し縮んで、離すと戻る。このフィードバックがあるだけでアプリの質感が上がります。
import { Pressable } from 'react-native';
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
} from 'react-native-reanimated';
function ScaleButton({ onPress, children }) {
const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
return (
<Pressable
onPressIn={() => {
scale.value = withSpring(0.95);
}}
onPressOut={() => {
scale.value = withSpring(1);
}}
onPress={onPress}
>
<Animated.View style={animatedStyle}>
{children}
</Animated.View>
</Pressable>
);
}
スワイプで削除するリストアイテム
React Native Gesture Handlerと組み合わせると、スワイプジェスチャーに連動したアニメーションも実装できます。
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
runOnJS,
} from 'react-native-reanimated';
function SwipeableItem({ onDelete }) {
const translateX = useSharedValue(0);
const gesture = Gesture.Pan()
.onUpdate((event) => {
translateX.value = event.translationX;
})
.onEnd(() => {
if (translateX.value < -100) {
runOnJS(onDelete)();
} else {
translateX.value = withSpring(0);
}
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: translateX.value }],
}));
return (
<GestureDetector gesture={gesture}>
<Animated.View style={animatedStyle}>
{/* アイテムの内容 */}
</Animated.View>
</GestureDetector>
);
}
まとめ
React Native Reanimatedは、モバイルアプリのアニメーション実装において、正直なところ標準のAnimated APIより圧倒的に使いやすいです。
- UIスレッドで動作するため120fps以上のパフォーマンス
- 宣言的なAPIで直感的に書ける
- ジェスチャーやセンサーとの統合が容易
- 豊富なイージング関数とアニメーション関数
学習コストはそれなりにありますが、一度覚えてしまえば「なぜもっと早く使わなかったのか」と思うレベルです。
React Nativeでアプリを作っていて、アニメーションの品質に悩んでいるなら、導入を検討してみてください。ユーザー体験が確実に向上します。
公式ドキュメントも充実しているので、より詳しい情報は公式サイトを参照してください。