Rorkで作ったアプリのアニメーションが、実機で確認するとカクカクします。そんな経験はないでしょうか。
プレビューでは問題なく動いているのに、TestFlightで試したら明らかにスムーズでない——個人開発をしていると、このギャップに気づくのは公開直前や公開後になりがちです。私自身、この問題に初めて直面したとき「Rorkが生成したコードだから仕方ない」と思い込んでそのままにしていた時期がありました。
ところが原因を調べてみると、多くの場合はたった1行の修正で解決できることがわかりました。フレームドロップが起きる根本的な仕組みと、診断から修正までの具体的な手順を実例とともに整理しました。
なぜアニメーションがカクつくのか — JSスレッドとUIスレッドの関係
React Nativeは、JavaScriptを処理するJSスレッドと、画面描画を担当するUIスレッドの2本柱で動いています。Rorkが生成するコードの多くはAnimated APIを使いますが、デフォルト設定ではアニメーションの計算がJSスレッド上で行われます。
問題は、JSスレッドがAPI通信やStateの更新など他の処理に追われると、アニメーションの計算が後回しになることです。60fpsを維持するには毎秒60回の計算が必要ですが、JSスレッドが詰まると1フレームが16.67msを超え、目に見えるカクつきとして現れます。
// ❌ Rorkが生成しがちなコード例 — JSスレッドで計算するためカクつく原因になる
const fadeAnim = useRef(new Animated.Value(0)).current;
Animated.timing(fadeAnim, {
toValue: 1,
duration: 300,
// useNativeDriver が指定されていない(暗黙的にfalseになる)
}).start();UIスレッドはJSスレッドとは独立して動くため、アニメーション計算をUIスレッドに委譲できれば、JSスレッドが混雑してもアニメーションはスムーズに動き続けます。
フレームドロップを診断する方法
修正の前に、まず本当にフレームドロップが起きているかを確認しましょう。React Nativeには開発ビルドで使えるパフォーマンスモニターが標準搭載されています。
実機でExpo開発ビルドを起動した状態で、メニューを開いて「Show Performance Monitor」を選択します。するとUI FPSとJS FPSの2つの数値がリアルタイムで表示されます。
見るべき数字:
UI FPSが60に近く、JS FPSだけが低下している場合はJSスレッドでアニメーション計算をしているサインです- 両方が低下している場合は、重いレンダリング処理が別の場所に潜んでいる可能性があります
Rork Companionを使っている場合は、コンソールログにAnimated: useNativeDriver was not specifiedという警告が出ていないかも確認してください。この警告が出ていれば、次の修正が直接効きます。
最もよくある原因と修正コード
① useNativeDriver: true を追加する
Rorkが生成するコードで最も多いのは、useNativeDriverが未指定またはfalseになっているケースです。
// ✅ 修正後 — UIスレッドで計算し、JSスレッドの混雑に影響されない
const fadeAnim = useRef(new Animated.Value(0)).current;
Animated.timing(fadeAnim, {
toValue: 1,
duration: 300,
useNativeDriver: true, // ← この1行を追加するだけ
}).start();
// 複数アニメーションの組み合わせも同様に指定する
Animated.parallel([
Animated.timing(fadeAnim, { toValue: 1, duration: 300, useNativeDriver: true }),
Animated.spring(slideAnim, { toValue: 0, useNativeDriver: true }),
]).start();重要な制限: useNativeDriver: trueは、opacity・transform(translateX, translateY, scale, rotate)など、レイアウトを変えないプロパティにのみ使用できます。width・height・backgroundColorなどレイアウト変更を伴うプロパティには使用できません。
// ❌ useNativeDriver: true を使えないプロパティ例(エラーになる)
Animated.timing(widthAnim, {
toValue: 200,
duration: 300,
useNativeDriver: true, // width はネイティブドライバー非対応
}).start();
// ✅ 代替: transform.scaleX で横幅を変えているように見せる
Animated.timing(scaleAnim, {
toValue: 2,
duration: 300,
useNativeDriver: true,
}).start();② Reanimatedへの移行(複雑なアニメーションの場合)
スクロール連動アニメーションや、ジェスチャーと組み合わせた複雑な動きが必要な場合は、react-native-reanimatedを使う方が根本的な解決になります。Expoのマネージドワークフローではすでにインストール済みです。
// react-native-reanimated を使ったスクロール連動フェードイン
import Animated, {
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated';
function AnimatedCard() {
const opacity = useSharedValue(0);
useEffect(() => {
opacity.value = withTiming(1, { duration: 400 });
}, []);
const animatedStyle = useAnimatedStyle(() => ({
opacity: opacity.value,
}));
return (
<Animated.View style={[styles.card, animatedStyle]}>
{/* コンテンツ */}
</Animated.View>
);
}ReanimatedのワークレットはデフォルトでUIスレッド上で直接実行されるため、JSスレッドを完全にバイパスします。スクロール連動やドラッグアニメーションでは、この差が体感として明確に現れます。
③ FlatListスクロール時にカクつく場合
アニメーション自体に問題がなく、スクロール時にカクつく場合は、FlatListのアイテムレンダリングが原因であることがほとんどです。
// ❌ スクロールのたびに重い計算が走るパターン
<FlatList
data={items}
renderItem={({ item }) => (
<View style={[styles.item, { backgroundColor: item.color }]}>
<Image source={{ uri: item.imageUrl }} />
<Text>{item.title}</Text>
</View>
)}
/>
// ✅ React.memoでアイテムコンポーネントをメモ化する
const ItemComponent = React.memo(({ item }) => (
<View style={[styles.item, { backgroundColor: item.color }]}>
<Image source={{ uri: item.imageUrl }} />
<Text>{item.title}</Text>
</View>
));
<FlatList
data={items}
renderItem={({ item }) => <ItemComponent item={item} />}
windowSize={5} // 画面外の描画範囲を絞る
initialNumToRender={8} // 初期表示件数を必要最小限に
maxToRenderPerBatch={5} // 一度に追加するアイテム数を制限
/>④ 画面遷移直後のカクつき — 重い処理を遷移完了後に遅延させる
画面遷移のアニメーション中にデータ取得や重い初期化を走らせると、遷移そのものがカクつきます。InteractionManagerを使うと、アニメーション完了後まで重い処理を遅延できます。
import { InteractionManager } from 'react-native';
useEffect(() => {
const task = InteractionManager.runAfterInteractions(() => {
// 遷移アニメーション完了後に実行される
loadHeavyData();
});
return () => task.cancel();
}, []);私のアプリでは、一覧→詳細の遷移でこのパターンに変えただけで「遷移の引っかかり」の体感が明確に変わりました。遷移直後にuseEffectで重い処理を即実行しているコードは、Rorkの生成コードに限らずよく見かける形なので、遷移系のカクつきではまずここを疑ってみてください。
Rorkへのプロンプトで最初から問題を防ぐ
修正よりも、最初から問題を発生させないプロンプトを使う方が効率的です。Rorkにアニメーションを追加させる際は、以下のように明示的に指示するとuseNativeDriverが最初から設定された状態でコードが生成されやすくなります。
「フェードインアニメーションを追加してください。
useNativeDriver: true を必ず使用し、60fps を維持できる実装にしてください。」
「スクロール連動のアニメーションは react-native-reanimated を使用してください。」
実際に試したところ、明示的に指定することで生成コードの品質が安定しました。指定なしだと、シンプルなケースではuseNativeDriverを付けてくれることもありますが、複雑なアニメーションでは省略されることがあります。
修正後の確認チェックリスト
修正後は以下を実機(TestFlightビルド)で確認してください。シミュレーターはパフォーマンスが実機と大きく異なるため、最終確認は必ず実機で行いましょう。
- パフォーマンスモニターで
UI FPSとJS FPSが両方60近くを維持しているか - スクロール中もアニメーションがスムーズに動くか
- コンソールに
useNativeDriver was not specified警告が出ていないか - 古い端末(iPhone XS相当)でも許容できるスムーズさか
アニメーションのスムーズさは、ユーザーがアプリの「質」を感じる重要な要素です。useNativeDriver: trueの追加という、たった1行の修正で体験が大きく変わるケースが多いので、カクつきが気になったらまずここを確認してみてください。同じ症状で悩んでいる方の助けになれば幸いです。