今、あなたはどちらかの状況にいるはずです。デザイナーからLottieのJSONを渡され、「これを今日中にアプリに組み込める?」と言われているか、あるいは実装は済んだものの、実機での動作や起動時間、リリースビルドにおいてパフォーマンスの重さを感じ始めているかです。
ここでLottie React Nativeの真価が問われます。単純なデモを作るのは簡単ですが、プロダクション環境に耐えうる実装はそうではありません。その差は、インストール方法、再生制御のやり方、そしてアニメーションファイルを単なるアセットとして扱うか、パフォーマンス予算の一部として扱うかにあります。
目次
- React NativeアプリにLottieが不可欠な理由
- Lottie開発環境のセットアップ
- 最初のLottieアニメーションを表示する
- Lottieアニメーション制御をマスターする
- 本番アプリのパフォーマンスチューニング
- Lottieの一般的なトラブルシューティング
React NativeアプリにLottieが不可欠な理由
React Nativeで洗練されたプロダクトのアニメーションを手動で再現しようとしたことがあれば、その苦労は身に染みているはずです。細かな動きのディテールは、タイミングのロジックや補間、プラットフォーム特有の挙動の塊へと変わります。見た目は「惜しい」状態になっても、デザイナーが意図したクオリティには到底及びません。
Lottieはそのワークフローを一変させました。2016年にAirbnbがLottieをオープンソース化したことで、エンジニアがフレームごとに作り直す必要がなくなり、デザイナーが直接アニメーションを実装できるようになり、モバイルアニメーションのあり方が変わりました。一部のエンタープライズ環境では、この変化によってモバイルアプリの開発コストが 最大40%。出典は AirbnbのLottie概要.
デザインとエンジニアリングの対立を解消する
Lottie React Nativeの大きな利点は、単に「JSONで綺麗なアニメーションを実現できる」ことだけではありません。それは「関心の分離」にあります。デザイナーはAfter Effectsで制作し、Bodymovinで書き出します。開発者は、動きをカスタムなcodeに変換するのではなく、ネイティブベースの再生機能でその出力をレンダリングするだけで済みます。
これが重要な理由は、アニメーションの実装範囲が広がりがちだからです。たった一つの「お祝い演出」のアニメーションであっても、デザインレビュー、プロダクトレビュー、Android/iOSそれぞれの挙動、アクセシビリティ、そして起動パフォーマンスにまで影響を及ぼします。Lottieは、その影響範囲を最小限に抑えてくれます。
実践ルール: 単純なopacityやtranslateのトランジションだけが必要なときではなく、アニメーションがプロダクト体験の一部であるときにLottieを使いましょう。
ユーザー体験(UX)の観点からもメリットがあります。モーションはフィードバックを与え、アクションを確定させ、ローディング状態の退屈さを軽減します。チームがUIの洗練度、継続率、あるいは信頼性を真剣に検討しているなら、アニメーションもその議論の一部です。より広範な アプリのユーザー体験に関する議論は たいてい同じ結論に落ち着きます。静的な画面よりも素早いフィードバックの方が勝る、という結論です。
Lottieが最も適しているケース
Lottie React Nativeが最も効果的なケース:
- ブランド独自のマイクロインタラクション 「いいね」、保存、チェックマーク、購入完了状態など
- オンボーディングのイラスト 動画ファイルを配信せずに、カスタム感を演出したい場合
- ローディングや空の状態(Empty States) 静的なUIでは物足りなさを感じる場面
- 機能紹介(Feature Education) GIFやMP4を埋め込まずに、動きを取り入れたい場合
ただし、あらゆるアニメーションの問題を解決できるわけではありません。単純な画面遷移であれば、React Native標準のアニメーションツールの方がシンプルな場合が多いです。また、非常に大規模、あるいは高度にインタラクティブなモーションシステムの場合、JSON形式がメリットではなくトレードオフになることもあります。このトレードオフはプロダクション環境でより重要になりますが、多くのチュートリアルはこの点に触れないまま終わってしまいます。
Lottie開発環境のセットアップ
インストール手順は、まず以下の決定によって決まります: Expo managed workflowか、bare React Nativeか. メンタルモデルを混同しないでください。多くのセットアップエラーは、Expo環境でbare workflowのガイドに従ったり、Expoがすべてのネイティブ詳細を抽象化してくれると思い込んだりすることで発生します。

インストール前にワークフローを選択する
Expoを使用しており、最短でセットアップを完了したい場合は、カスタムネイティブコードが必要だと分かっていない限りExpoのパスを維持してください。bareアプリを使用している場合や、直接制御が必要なネイティブモジュールに既に依存している場合は、通常のネイティブ依存関係としてインストールし、すぐにiOSとAndroidの両方のビルドを確認してください。
プロジェクトのタイプに合わせてセットアップを統一することで、デバッグがいかに容易になるかを過小評価しているチームは多いです。また、カスタムネイティブ統合を構築している多くのチームが、早い段階から比較検討しているのが Expo EASの代替案としてのCapgoです アプリの変更が困難になるのを待つのではなく、早い段階で検討しましょう。
Expo managedセットアップ
Expo managedアプリの場合は、最小限の構成に留めます。
-
パッケージをインストール
npx expo install lottie-react-native -
Metroを再起動
npx expo start -c -
実機またはシミュレーターで検証する まずはローカルのJSONファイルを使って、ごく小さなアニメーションを表示させることから始めましょう。巨大なアセットのデバッグと新規インストールを同時に行うのは避けてください。
Expoにおける実用的な注意点:
- まずはローカルファイルを優先する: ライブラリが動作することを確認したいだけの時に、リモートのアニメーションを使うとネットワークの影響(ノイズ)が混じってしまいます。
- リリース時の挙動を早めにテストする: 開発モードでは、タイミングやパフォーマンスに関する問題が隠れてしまうことがあります。
- アセットのパスに注意する: JSONファイルの配置ミスは、「何も表示されない」という問題の最も一般的な原因の一つです。
Expoは「動く」状態にする最短ルートですが、それが「スケールする」状態にする最短ルートとは限りません。
Bare React Native構成の場合
Bareプロジェクトでは、すぐにネイティブ依存関係をインストールして検証してください。
-
パッケージをインストール
npm install lottie-react-native -
iOS podsをインストール
cd ios && pod install && cd .. -
アプリを再ビルド
npx react-native run-iosまたは
npx react-native run-android
多くのクイックスタートガイドで見落とされがちな点ですが、インストール後は、不具合を疑う前に必ずフルネイティブビルドを行ってください。ネイティブ依存関係が正しくコンパイルされていない場合、Hot reloadでは解決できません。
時間を節約するBare workflowの確認事項
次に進む前に、この短いチェックリストを確認してください:
| 確認 | なぜ重要なのか |
|---|---|
| インストール後に再ビルド | ネイティブモジュールには再コンパイルが必要 |
実行する pod install |
これがないとiOSの動作が不安定になります |
| まずはシンプルなローカルJSONを使いましょう | アセットの問題とインストール問題を切り分けられます |
| 早い段階で両プラットフォームをテストしてください | AndroidとiOSでは失敗の原因が異なる場合があります |
パッケージが正常にインストールされているのに最初のアニメーションが表示されない場合、それは通常インストール時の問題ではありません。多くの場合、アセットのパス、コンポーネントのサイズ、または再生設定に原因があります。
最初のLottieアニメーションを表示する
最初に動かすアニメーションは、極力シンプルにすべきです。ローカルファイル、固定サイズ、AutoplayはON、Loopは任意。条件付き再生やリモートJSON、レイヤーの多い複雑なアニメーションから始めないでください。

ローカルのアニメーションファイルを追加する
まだ作成していない場合は、assetフォルダを作成してください:
assets/
animations/
success.json
名前はシンプルに保ちましょう。スペースや特殊な記号、深い階層のフォルダは避けてください。目的は require() パスを明確に保つこと。
ブランドロゴのローディング画面や、起動後の遷移にLottieを使用する場合、起動プロセスに巨大なアニメーションを組み込むのは慎重に検討してください。特に、以下の設定を調整している場合は注意が必要です。 React Nativeのsplash screenの挙動.
LottieViewでレンダリングする
大きな画面ファイルに直接書き込むのではなく、専用のコンポーネントを作成しましょう:
import React from 'react';
import { View, StyleSheet } from 'react-native';
import LottieView from 'lottie-react-native';
export function SuccessAnimation() {
return (
<View style={styles.container}>
<LottieView
source={require('../assets/animations/success.json')}
autoPlay
loop={false}
style={styles.animation}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
alignItems: 'center',
justifyContent: 'center',
},
animation: {
width: 220,
height: 220,
},
});
これには3つのメリットがあります:
- ライブラリが正しくレンダリングされるか確認できる
- アセットのパスが正しく解決されるか確認できる
- 後から再生やサイズを調整するための独立した場所を確保できる
基本を飛ばすと、すぐに以下のような問題に直面します:
- widthやheightの未指定: アニメーション自体は存在していても、表示されないことがあります。
- 不正な
require()パス: Metroがファイルを見つけられません。 - 不正なエクスポート: 一部のJSONファイルは技術的には有効ですが、モバイル環境では期待通りに動作しない機能が含まれていることがあります。
最初のレンダリングはローカルで決定論的に保ちましょう。テストすべきはアーキテクチャではなく、インテグレーションです。
より良い初動画面のテスト方法
コンポーネントを、ニュートラルな背景のシンプルな画面に配置します:
import React from 'react';
import { SafeAreaView, StyleSheet } from 'react-native';
import { SuccessAnimation } from './src/SuccessAnimation';
export default function App() {
return (
<SafeAreaView style={styles.screen}>
<SuccessAnimation />
</SafeAreaView>
);
}
const styles = StyleSheet.create({
screen: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
},
});
iOSとAndroidの両方のシミュレーターで動作すれば、最初の大きなハードルは越えたことになります。そこからの次のステップは、アニメーションを増やすことではありません。宣言的なpropsを使うべき時と、refsを使って直接制御すべき時を学ぶことです。
Lottieアニメーション制御のマスター
Lottie React Nativeのバグの多くは、アニメーションがstateに反応する必要がある時に発生します。Autoplayは簡単です。「ユーザーがアイテムを『いいね』したらこのセグメントを再生し、『いいね』を解除したら逆再生し、コンポーネントの再レンダリング時にカクつかないようにする」といった実装になると、一気に複雑になります。

再生がシンプルな場合はpropsを使用する
インタラクティブ性を必要としない再生なら、propsだけで十分です。
<LottieView
source={require('../assets/animations/loading.json')}
autoPlay
loop
speed={1}
/>
このスタイルが適しているケース:
- ローディングインジケーター
- 受動的なオンボーディング用イラスト
- 装飾的な空の状態(Empty States)
宣言的で可読性が高いのがメリットです。コンポーネントがマウントされると再生が始まり、Reactが制御を維持します。アニメーションのロジックがpropsだけで完結できるなら、そのまま使いましょう。
より高度な宣言的なケースは、 progressアニメーションフレームを別の値に紐付ける場合です。動きを外部の進捗状況に連動させたいときには有効ですが、単発のトリガーイベントにはあまり向きません。
refsに移る前に、視覚的な比較を見てみましょう:
状態(state)でアニメーションを制御する場合はrefsを使用する
ユーザーのタップ、切り替え、アクションの完了などを行う場合は、通常refを使う方が安全です。実例では次のような違いがあります。 ハイブリッドフレームワークを使用する開発者の68%が、 useEffect hooksにおける不適切なrefの扱いに起因する、アニメーションのトリガー失敗を報告しています。そのため、 animation.current.play() において、信頼性の高いパターンを構築することが Capacitorに焦点を当てたトリガー失敗に関する議論.
で指摘されている通り、非常に重要です。この問題はハイブリッドアプリに限ったことではありません。React Native単体でも、refの再生成、マウント前の再生実行、あるいは不安定なEffectへのアニメーション呼び出しの紐付けなどによって頻繁に発生します。
import React, { useRef, useState } from 'react';
import { Pressable } from 'react-native';
import LottieView from 'lottie-react-native';
export function LikeButton() {
const animationRef = useRef<LottieView>(null);
const [liked, setLiked] = useState(false);
const onPress = () => {
if (!animationRef.current) return;
if (liked) {
animationRef.current.play(60, 0);
} else {
animationRef.current.play(0, 60);
}
setLiked(!liked);
};
return (
<Pressable onPress={onPress}>
<LottieView
ref={animationRef}
source={require('../assets/animations/like.json')}
loop={false}
autoPlay={false}
style={{ width: 96, height: 96 }}
/>
</Pressable>
);
}
信頼できる「Like/Unlike」パターン
このパターンは、状態が変化するたびに play() 内で useEffect 呼び出すよりも、本番環境での安定性に優れています。
仕組みの解説:
- イベントがアニメーションのトリガーを制御します: press イベントは再生を開始する安定したタイミングです。
- ref はローカルに保持され、永続します:
useRef不要な再レンダリングを防ぎます。 - コンポーネントは autoplay の競合を回避します: マウント時の挙動とユーザー操作による挙動が競合しないようにします。
避けたいよくある間違い:
-
ref が存在する前にトリガーしてしまう
もしanimationRef.currentnull の場合、再生されません。ガードしてください。 -
活用
autoPlay命令型コントロールを使う
再生のデフォルトオーナーを1つに決めます。 -
すべてを〜で動かす
useEffect
エフェクトは便利ですが、UIアクションでは、タイミングの問題をなくすどころか、むしろ増やしてしまうことがよくあります。
タップに反応するアニメーションなら、まずタップハンドラー内でトリガーしてください。〜を使うのは、
useEffectsource of truth がそのインタラクションの外にある場合に限ります。
本番アプリのパフォーマンスチューニング
Lottie React Nativeは、一見軽量に見えますが、チームが巨大なJSONファイルをアプリのbundleに詰め込み始めると、なぜ起動速度が低下したのかと頭を抱えることになるライブラリの一つです。問題はアニメーションそのものではなく、配信戦略にあります。

チームが陥りやすい罠
最もありがちなミスは、すべてのアニメーションをJavaScriptに直接bundleし、それらすべてを早すぎるタイミングでロードしてしまうことです。 Lottie JSONの不適切な配信に関するこのガイドによると、Lottie JSONのようなアセットでJS bundleを過負荷にすると、アプリの起動時間が ミドルレンジのデバイスで40%以上増加する可能性があります、これらをネイティブアセットに移動してオンデマンドでロードすることは、極めて重要な最適化です。
これは多くのチームが実際に経験していることと一致します。問題は小さな成功アニメーション1つではなく、その積み重なりです:
- オンボーディングのモーション
- ローダーの状態
- eコマースのリアクション
- ブランドの空状態画面
- それらと一緒にあるロケールファイルや、バンドルを圧迫する他のアセット
アプリの起動時のパフォーマンス予算にすでに問題があるなら、Lottieファイルは状況を一気に悪化させます。
まず何を最適化すべきか
まずはエクスポート自体から始めましょう。雑なアニメーションのエクスポートは、後でパース、メモリ、レンダリングの安定性でツケを払うことになる複雑さを抱えています。デザイナーのエクスポートをすべてそのまま受け入れるのはやめましょう。
この本番用チェックリストを使ってください:
- リリース前にJSONを圧縮する: ファイルが小さいほどロードしやすく、起動時の肥大化も抑えられます。
- 重要度の低いアニメーションはJS bundleから外しましょう: 起動時の code は、アプリがすぐに必要とする処理だけに集中させましょう。
- アニメーションを必要に応じてロードする: 画面や操作が必要とするタイミングでレンダリングします。
- 古い端末での挙動を確認する: 最新のシミュレーターは、負荷の高い再生処理を隠してしまうことがあります。
- 起動時の装飾として大きなLottieファイルを使用するのは避けましょう: 最初のインタラクションに不可欠でないなら、アプリの起動と競合させるべきではありません。
モバイルパフォーマンスに本気で取り組むチームにとって、 AppLighterのモバイルパフォーマンスガイドは、 アニメーションの判断を、アプリ起動、レンダリング、フレームワークのトレードオフという大きな文脈で捉えられるため、併読におすすめです。
厳しい現実が一つあります: 最初のインタラクションを遅らせるような美しいアニメーションは、デザインの勝利ではなく、通常はプロダクトのバグです。
また、React Native単体で考えるべきではありません。ハイブリッドスタックを採用しているチームも同様のアセット読み込み問題に直面しており、 Capacitor アプリ向けの広範な アニメーション・パフォーマンスの指針は、Lottieの選定にもそのまま当てはまります。
ローカルファイル vs リモート配信
ローカルファイルは予測可能です。オフラインでも動作し、ネットワークの変動がなく、テストも容易です。ただし、バンドルサイズが肥大化しやすい側面もあります。
リモート配信はバイナリを軽量に保てますが、アニメーションの可用性、キャッシュ、フォールバックへの考慮が必要になります。重要度の低いモーションなら許容できますが、購入完了や認証成功といった主要なUX状態ではリスクとなります。
実用的な使い分けは以下の通りです:
| アセットの種類 | 推奨されるデフォルト |
|---|---|
| コアなインタラクションのアニメーション | ローカル保存・最適化・軽量化 |
| 時折使用するプロモーション用アニメーション | リモート取得(フォールバックあり) |
| 起動時のパスアニメーション | どうしても必要な場合のみローカルに保持 |
| たまにしか使わない機能の図解 | オンデマンド読み込み |
このセクションのルールを1つだけ適用するなら、これです: Lottie JSONは単なる装飾ではなく、パフォーマンスに影響するアセットとして扱う.
Lottieのよくある問題とトラブルシューティング
Lottieが動かない原因は、大抵よくあるものです。パスの間違い、サイズ指定漏れ、refのタイミング、JSONの肥大化など。デバッグの近道は、変数を減らすことです。
Androidでアニメーションがレンダリングされない
まず、JSONファイルが正しく解決されているか確認してください。その上で、コンポーネントに明示的なサイズを指定します。
<LottieView
source={require('../assets/animations/success.json')}
autoPlay
style={{ width: 200, height: 200 }}
/>
それでも解決しない場合は、正常に動作することがわかっている別のアニメーションに差し替えてみてください。これで問題がファイルにあるのか、セットアップにあるのかを切り分けられます。
古いデバイスで再生がカクつく
これは通常、コンポーネント API ではなく、アセット側に原因があります。
以下の修正方法を試してください:
- アニメーションの複雑さを軽減する: ソースファイルが重い場合は、より軽量な書き出しを依頼してください。
- 遅延読み込みを行う: 初期画面の描画処理と競合させないようにします。
- 圧縮版をテストする: 圧縮したファイルで動作が改善されるなら、そこがボトルネックです。
- 複数のLottieビューを同時に表示しないようにする: 1つの画面に複数のアニメーションがあると、負荷が高すぎる場合があります。
ref が null になる、または play が動作しない
ref が null になる原因の多くは、マウント前にトリガーが実行されたか、コンポーネントが条件付きで削除されたことです。
if (animationRef.current) {
animationRef.current.play();
}
を使用して ref を安定させ、 useRefアニメーションコンポーネントを不必要に再生成しないでください。ローカルビルドで奇妙な挙動が繰り返される場合は、古いキャッシュをクリアすると解決することがあります。簡単な Yarn キャッシュのクリーンアップ だけで、開発中のアセットの誤った挙動を解消できることもあります。
画面サイズによってアニメーションが崩れる
アニメーションにレイアウトを依存させないでください。コンテナ内に配置し、意図したサイズを指定しましょう。
- アイコンやリアクションには固定の範囲を使用する
- 大きなイラストにはアスペクト比を考慮したラッパーを使用する
- 書き出し時の構成を確認せずに、全幅に引き伸ばさないようにしてください
「Lottieが動かない」という報告の多くは、実際にはレイアウト、アセット、またはタイミングの問題です。ライブラリ自体は、指示通りに正しく動作していることがほとんどです。
デバッグを素早く終わらせるコツは、一度すべての高度なプロップを削除し、中央配置のビューでローカルのアニメーションを1つだけレンダリングすることです。そこから段階的に設定を戻していく手法は、複雑な本番画面を凝視するよりも早く問題を特定できます。
Capgo を使えば、ストアの審査を待たずに JavaScript、アセット、設定の修正を Capacitor アプリに配信できます。ハイブリッドアプリを運用しており、より安全なアップデート配信、段階的なロールアウト、フロントエンドの問題からの迅速な復旧を実現したいなら、 Capgo 検討する価値があります。