framer-motion、lottie-react-native、react-native-animatable、react-native-reanimated、react-spring、react-transition-groupはすべて、ReactやReact Nativeアプリケーションにアニメーションを導入するためのライブラリですが、それぞれ設計思想と適用領域が異なります。
framer-motionは主にWeb向けで、宣言的なAPIと物理ベースのアニメーションにより高品質なインタラクションを実現します。lottie-react-nativeはLottie JSONファイルをReact Nativeで再生するための専用ライブラリです。react-native-animatableはシンプルなプリセットアニメーションを提供し、導入が容易ですが柔軟性に欠けます。react-native-reanimatedはネイティブスレッド上でアニメーションを実行でき、パフォーマンス重視の複雑なジェスチャー対応UIに適しています。react-springは物理法則に基づくアニメーションエンジンで、WebとReact Nativeの両方をサポートします。react-transition-groupはCSSトランジションやアニメーションをReactコンポーネントのライフサイクルに連動させるための低レベルなユーティリティです。
アニメーションは現代のUIにおいて必須の要素ですが、Reactエコシステムには目的や能力が大きく異なる複数のライブラリがあります。この記事では、framer-motion、lottie-react-native、react-native-animatable、react-native-reanimated、react-spring、react-transition-groupの6つを、実際の開発シナリオに基づいて深く比較します。
まず重要なのは、ターゲットプラットフォームです。
framer-motion, react-spring (Web版), react-transition-grouplottie-react-native, react-native-animatable, react-native-reanimatedreact-spring(WebとReact Nativeの両方をサポート)⚠️ 注意:
framer-motionはReact Nativeでは動作しません。同様に、lottie-react-nativeはWebでは使えません(Web向けにはlottie-webまたはlottie-reactを使用)。
パフォーマンスに大きく影響するのが「どこでアニメーションが実行されるか」です。
react-native-animatable, react-spring(一部)
react-native-reanimated
framer-motion, react-transition-group, lottie-react-native
最もシンプルなケース — コンポーネントの表示時にフェードイン — で各ライブラリの使い方を見てみましょう。
framer-motion(Web)import { motion } from 'framer-motion';
function FadeIn() {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
>
Hello World
</motion.div>
);
}
lottie-react-native(React Native)Lottieは事前作成されたJSONアセットを再生するため、フェードインのような単純なアニメーションには不向きですが、一応表示は可能です。
import LottieView from 'lottie-react-native';
function LottieFade() {
return (
<LottieView
source={require('./fade-in.json')}
autoPlay
loop={false}
/>
);
}
react-native-animatable(React Native)import Animatable from 'react-native-animatable';
function FadeIn() {
return (
<Animatable.View animation="fadeIn" duration={500}>
<Text>Hello World</Text>
</Animatable.View>
);
}
react-native-reanimated(React Native)import { View } from 'react-native';
import Animated, { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated';
function FadeIn() {
const opacity = useSharedValue(0);
// 初期表示時にアニメーション開始
useEffect(() => {
opacity.value = withTiming(1, { duration: 500 });
}, []);
const animatedStyle = useAnimatedStyle(() => ({
opacity: opacity.value,
}));
return (
<Animated.View style={[{ padding: 20 }, animated-style]}>
<Text>Hello World</Text>
</Animated.View>
);
}
react-spring(WebまたはReact Native)// Webの場合
import { animated, useSpring } from '@react-spring/web';
function FadeIn() {
const props = useSpring({ opacity: 1, from: { opacity: 0 }, config: { duration: 500 } });
return <animated.div style={props}>Hello World</animated.div>;
}
// React Nativeの場合
import { AnimatedView, useSpring } from '@react-spring/native';
function FadeIn() {
const props = useSpring({ opacity: 1, from: { opacity: 0 }, config: { duration: 500 } });
return <AnimatedView style={props}><Text>Hello World</Text></AnimatedView>;
}
react-transition-group(Web)import { CSSTransition } from 'react-transition-group';
import './Fade.css'; // CSSでopacityのtransitionを定義
function FadeIn({ in: inProp }) {
return (
<CSSTransition in={inProp} timeout={500} classNames="fade">
<div>Hello World</div>
</CSSTransition>
);
}
/* Fade.css */
.fade-enter {
opacity: 0;
}
.fade-enter-active {
opacity: 1;
transition: opacity 500ms;
}
次に、より高度なユースケース — ドラッグ可能なカードを左右にスワイプしてスナップする — を比較します。
framer-motion(Web)import { motion, useMotionValue, useTransform } from 'framer-motion';
function DraggableCard() {
const x = useMotionValue(0);
const rotate = useTransform(x, [-100, 100], [-10, 10]);
return (
<motion.div
drag="x"
dragConstraints={{ left: -100, right: 100 }}
style={{ x, rotate }}
dragSnapToOrigin
>
Swipe me
</motion.div>
);
}
react-native-reanimated(React Native)import { PanGestureHandler } from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedGestureHandler,
useAnimatedStyle,
withSpring,
} from 'react-native-reanimated';
function DraggableCard() {
const translateX = useSharedValue(0);
const gestureHandler = useAnimatedGestureHandler({
onStart: (_, ctx) => {
ctx.startX = translateX.value;
},
onActive: (event, ctx) => {
translateX.value = ctx.startX + event.translationX;
},
onEnd: () => {
if (Math.abs(translateX.value) > 50) {
translateX.value = withSpring(translateX.value > 0 ? 100 : -100);
} else {
translateX.value = withSpring(0);
}
},
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: translateX.value }],
}));
return (
<PanGestureHandler onGestureEvent={gestureHandler}>
<Animated.View style={[styles.card, animatedStyle]}>
<Text>Swipe me</Text>
</Animated.View>
</PanGestureHandler>
);
}
react-spring(Web)import { useDrag } from '@use-gesture/react';
import { animated, useSpring } from '@react-spring/web';
function DraggableCard() {
const [{ x, rotate }, api] = useSpring(() => ({ x: 0, rotate: 0 }));
const bind = useDrag(({ offset: [ox], last }) => {
if (last) {
if (Math.abs(ox) > 50) {
api.start({ x: ox > 0 ? 100 : -100, rotate: ox > 0 ? 10 : -10 });
} else {
api.start({ x: 0, rotate: 0 });
}
} else {
api.start({ x: ox, rotate: ox / 10 });
}
});
return <animated.div {...bind()} style={{ x, rotate }}>Swipe me</animated.div>;
}
💡 注:
lottie-react-native、react-native-animatable、react-transition-groupはこのようなインタラクティブなドラッグ操作には対応していません。
react-native-animatableの現状react-native-reanimatedの制約console.log、カスタム関数)をアニメーション中に直接実行できません。react-native-gesture-handlerとの併用が必須です。framer-motionの強みlayoutプロパティを使うと、要素のサイズや位置が変更された際に自動でスムーズなトランジションを適用できます。whileHover、whileTapなどのインタラクションハンドラが組み込みで提供されています。<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95 }}
>
Click me
</motion.button>
UI状態(例:開閉、選択)に応じてアニメーションを切り替えるケースです。
react-springconst [{ height }, api] = useSpring(() => ({ height: 0 }));
const toggle = () => {
api.start({ height: isOpen ? 0 : 200 });
};
return (
<animated.div style={{ height, overflow: 'hidden' }}>
Content
</animated.div>
);
framer-motion<motion.div
animate={{ height: isOpen ? 200 : 0 }}
transition={{ duration: 0.3 }}
style={{ overflow: 'hidden' }}
>
Content
</motion.div>
react-transition-group<CSSTransition in={isOpen} timeout={300} classNames="slide">
<div>Content</div>
</CSSTransition>
/* CSSでheightのtransitionを定義 */
| ユースケース | 推奨ライブラリ |
|---|---|
| Web向け高品質インタラクション(ページ遷移、ドラッグ、スクロール連動) | framer-motion |
| React NativeでLottie JSONを再生 | lottie-react-native |
| React Nativeで簡単なフェード・スライドアニメーション(小規模プロジェクト) | react-native-animatable(※新規プロジェクトでは避けるべき) |
| React Nativeで高性能なジェスチャー対応UI(例:カスタムナビゲーション) | react-native-reanimated |
| 物理ベースの自然なアニメーションをWebとReact Nativeで共通化 | react-spring |
| CSSトランジションをReactのマウント/アンマウントに連動 | react-transition-group |
framer-motionが多くのケースで最適です。ただし、非常にシンプルなトランジションならreact-transition-groupで十分な場合もあります。lottie-react-nativereact-native-reanimatedreact-springreact-native-animatable(ただし長期的には移行を検討)どのライブラリを選ぶにせよ、アニメーションは「目的」ではなく「手段」であることを忘れないでください。ユーザー体験を向上させるために、適切なツールを選びましょう。
react-transition-groupは、CSSトランジションやキーフレームアニメーションをReactのマウント・アンマウントタイミングに連動させたい場合に使います。例えば、モーダルの表示・非表示やリストアイテムの追加・削除時の滑らかな演出に適しています。独自のアニメーションエンジンは持たず、既存のCSSと連携する点が特徴です。
framer-motionは、Web向けの高度なインタラクション(ページ遷移、ドラッグ操作、スクロール連動など)を実装したい場合に最適です。特にデザインシステムとの統合や、モーションの微調整が必要なプロダクトで力を発揮します。ただし、React Nativeでは使用できません。
react-native-reanimatedは、ネイティブスレッドでアニメーションを実行できるため、60fpsを維持する必要がある複雑なUI(例:カスタムナビゲーション、スワイプ可能なリスト、高度なジェスチャー)に最適です。ただし、学習コストが高く、デバッグも難しいため、単純なアニメーションにはオーバーキルです。
react-springは、物理ベースの自然な動き(バネ、減衰など)をWebとReact Nativeの両方で実現したい場合に有効です。アニメーションの状態管理が直感的で、パフォーマンスも良好ですが、Lottieのような事前作成済みアセットの再生には使えません。
lottie-react-nativeは、デザイナーがAdobe After Effectsで作成したLottie JSONアニメーションをReact Nativeアプリ内で忠実に再生したい場合に選択します。ベクター形式で軽量かつスケーラブルですが、インタラクティブな制御(例:ユーザー入力に応じた変化)には向いていません。
react-native-animatableは、すぐに使えるプリセットアニメーション(フェードイン、スライドなど)を手軽に導入したい場合に向いています。小規模なプロジェクトやプロトタイピングに便利ですが、カスタムアニメーションや高性能なジェスチャー対応UIには不向きです。
ATTENTION! To address many issues that have come up over the years, the API in v2 and above is not backwards compatible with the original
React addon (v1-stable).For a drop-in replacement for
react-addons-transition-groupandreact-addons-css-transition-group, use the v1 release. Documentation and code for that release are available on thev1-stablebranch.We are no longer updating the v1 codebase, please upgrade to the latest version when possible
A set of components for managing component states (including mounting and unmounting) over time, specifically designed with animation in mind.
TypeScript definitions are published via DefinitelyTyped and can be installed via the following command:
npm install @types/react-transition-group
Clone the repo first:
git@github.com:reactjs/react-transition-group.git
Then run npm install (or yarn), and finally npm run storybook to start a storybook instance that you can navigate to in your browser to see the examples.