mcpField
E
expo-animation

expo-animation

github.com/expoデザイン・UI3.5k16.3k 回インストール今日更新
リポジトリ

expo-animation について

expo-animationは、ExpoでReact Nativeアニメーションを実装するための構築スキルです。モバイル開発者が動きの必要性を判断し、UIランタイムで応答性を保つ方法を選べるよう、視差効果の軽減と実機での検証も扱います。

主な機能

  1. 01アニメーションの必要性と目的の判断
  2. 02UIランタイムでのReanimated worklets
  3. 03ジェスチャーに対応するアニメーション手法
  4. 04時間、スプリング、プロパティの指針
  5. 05視差効果の軽減とデバイステスト

活用シーン

  1. 01タップやジェスチャーのフィードバック実装
  2. 02シート、モーダル、画面遷移の構築
  3. 03低速デバイスでのアニメーションの遅延回避

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/expo/skills --skill expo-animation

インストール後、Claude Code で/expo-animationを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

LICENSE
RECIPES.md
agents/
openai.yaml

詳細

Expo でアニメーションを構築する

このスキルは Emil Kowalski と共同で作成され、ほかの便利なアニメーションスキルとともに、emilkowalski/skills リポジトリでも公開されています。

React Native 向けの構築スキルです。モーションの要望を、シミュレーターでも開発モードの最新機種でもなく、実機での厳しいレビューに耐えられる実装へと変換します。

モバイルではアニメーションに関する要素が3つ変わり、このスキルの内容はすべてそれに基づいています。

  1. ホバーはありません。 Web でホバーに割り当てられているあらゆる操作上の手がかりは、押下、位置、または何もない状態で表現する必要があります。
  2. ランタイムは2つあります。 Worklet(Reanimated 4)では、この違いが明確になります。React Native ランタイムでは React がレンダリングを行い、アプリのロジックが実行されます。一方、UI ランタイムでは Worklet が毎フレーム実行されます(バックグラウンド処理用に、オプションでワーカーランタイムを追加することもできます)。RN ランタイムに触れるアニメーションは、アプリがほかの処理を行った途端にカクつきます。モーションを UI ランタイム上で動かし続けることが、技術の要です。
  3. ユーザーの指が要素に触れています。 ジェスチャーが主要な入力方法なので、途中で中断できることや速度を引き継ぐことは、仕上げではなく基本要件です。

開発姿勢

あなたはアニメーションを自ら構築する上級モバイルエンジニアです。判断を下し、その理由を一行で述べ、コードを書いてください。モーションの選択肢をメニューのように提示してはいけません。

失敗のパターンは2つあります。より深刻なのは1つ目です。

  1. アニメーションさせるべきでないものを動かすこと。 以下の判定基準は、コードが一行も不要な場合があることを前提としています。
  2. 適切な対象を誤ったスレッドで動かすこと — フレームごとの setState、PanResponder、アニメーションする height などです。開発中に自分のスマートフォンで見ると問題なくても、3年前の Android では20fpsまで低下します。

厳守事項

  1. 順番どおりに進めてください。 ステップ1と2が、以降すべての前提です。
  2. コアの Animated ではなく Reanimated を使ってください。 コアの Animated はブリッジをまたがずにジェスチャーで動かすことができず、useNativeDriver が扱えるのも transform と opacity だけです。Reanimated の Worklet は UI スレッドで実行され、JS が処理中でも動き続けます。
  3. 値を概算しないでください。 カーブとスプリング設定には、以下の表の値を使用してください。
  4. アニメーションの提供時点で、モーション軽減にも対応してください。 後回しにしてはいけません。
  5. 実際の使用感は、サポート対象の中で最も遅いデバイスのリリースビルドで評価してください。 それ以外は検証済みとはみなせません。

構築手順

1. そもそもアニメーションさせるべきか?
頻度判断
1日に100回以上 — タブ切り替え、キーボードの開閉、スクロール、設定の切り替えアニメーションなし。 プラットフォームのデフォルト、または何もしません。ここで終了します。
1日に数十回 — 押下フィードバック、リストの移動、行の選択ほとんど気づかれない程度に限ります。150ms未満、または何もしません
たまに — シート、モーダル、トースト、オンボーディングのステップ標準的なアニメーション
まれ/初回のみ — 成功状態、空状態のイラスト、祝福の演出喜びを生み出す演出の予算はここで使います

タブの切り替えでスライドさせてはいけません。 タブは同列の項目であり、階層関係はありません。スライドは実際にはない奥行きを示唆し、ユーザーは1回のセッションで何十回もその負担を負うことになります。animation: 'none'。

この基準を満たさない要望には、その旨を伝えてコードを書かないでください。

2. 目的は何か?

先に一語で目的を名付けてから続けてください。フィードバック、空間的一貫性、状態の表示、唐突な変化の防止、説明、または喜びの演出(まれなケースのみ)です。

目的を名付けられない場合は、構築しないでください。

3. ツールを選ぶ — 動作する中で最も低コストなもの

順にたどり、最初に当てはまったものを使います。

要件ツール
ジェスチャーを伴わない状態駆動の変化 — 押下、トグル、色、値の切り替えReanimated CSS transition(スタイル内の transitionProperty)
ループ、多段階、または状態変化なしでマウント時に再生Reanimated CSS animation(animationName キーフレーム)
要素のマウント・アンマウント、またはリストの再配置Layout animations(entering / exiting / itemLayoutAnimation)
指が触れるものすべて、またはスクロールから導出されるものすべてuseSharedValue + Gesture + useAnimatedStyle
画面から画面へExpo Router のネイティブスタックオプション。 これを自作しないでください
それ自体が 1 つの画面であるボトムシートpresentation: 'formSheet' — 本物の UISheetPresentationController であり、無料で正確です
タブバーNativeTabs(expo-router/unstable-native-tabs から)— プラットフォームの本物のタブバーで、その挙動とトランジションも含まれます
コンテキストメニュー、長押しプレビューLink.Menu / Link.Preview(Expo Router、iOS 専用)— ネイティブのメニューとピークであり、JS で作り直さないでください
ラージタイトルに折りたたまれるヘッダーネイティブスタックの headerLargeTitleEnabled(iOS 専用、headerLargeTitle は非推奨)— スクロール worklet ではありません
引っ張って更新RefreshControl — シグネチャーとなるインタラクションの場合のみ自作します(しきい値のレシピを参照)
キーボードを追従する UIreact-native-keyboard-controller — キーボードの実際の位置を、フレームごとに、UI スレッド上で
ベクターイラスト、お祝い、空状態Lottie — イラスト専用で、UI の状態には決して使わないでください
巨大なアニメーションシーン、自由な描画@shopify/react-native-skia — ビュー階層自体がボトルネックになる場合のためのキャンバス

共有値に手を伸ばすのは、その値が連続的または中断可能な場合だけにしてください。押下時のスケールは CSS transition であり、ドラッグは共有値です。2 状態のトグルに worklet を使うのは、フェードのためにモーションライブラリをインストールするモバイル版に相当します。

依存関係。 npx expo install <package> でインストールしてください — プロジェクトの SDK に一致するバージョンを解決しますが、素の npm install ではそれができません。

要件パッケージ
アニメーションreact-native-reanimated + react-native-worklets
ジェスチャーreact-native-gesture-handler
ナビゲーション、シート、ネイティブタブ、メニューexpo-router
ハプティクスexpo-haptics
キーボード追従 UIreact-native-keyboard-controller(ルートに KeyboardProvider が必要 — キーボードのレシピを参照)
イラスト、お祝いlottie-react-native
非常に大きなアニメーションシーン、カスタム描画@shopify/react-native-skia
4. プロパティを選ぶ
  • transform と opacity は無料です。 それ以外はすべてレイアウトパスです。width、height、margin、padding、flex、top、left、gap は、そのノードとその兄弟に対して毎フレーム Yoga を再実行します。
  • 唯一の例外: 子を持たない絶対配置の要素 — タブのピル、プログレスバーのフィル。これはフローから外れているため他は再レイアウトされず、width をアニメーションさせれば scaleX が歪めてしまう角丸を保てます。
  • scale(0) は決して使わないでください。 scale(0.9〜0.97) + opacity: 0 から始めます。現実世界で何かが無から現れることはありません。
  • transform は配列であり、順序が重要です — [{ translateY }, { scale }] は移動後にスケールします。逆にすると、translate もスケールされます。乗算を望まない限り translate を先にしてください。
  • Android の影は elevation であり、elevation をアニメーションさせると毎フレーム影が再レンダリングされます。 代わりに、あらかじめ影を付けたレイヤーの opacity をアニメーションさせてください。
  • BlurView の intensity は決してアニメーションさせないでください。 Android では毎フレームブラーが再レンダリングされます。代わりに、静的な BlurView の opacity をクロスフェードさせてください。
  • translate ではパーセンテージが使えます そして要素自身のサイズに対する相対値です — translateY('100%') は、内容にかかわらずシートを自身の高さだけ移動させます。
5. タイミングかスプリングか

指を使った場合は、スプリングを使います。 スプリングは中断時にも速度を引き継ぎますが、タイミングカーブは最初からやり直します。それ以外はすべてタイミングを使います。

Reanimated のスプリングには、Apple のデザイナー向けパラメーターをそのまま指定します。mass/stiffness/damping ではなく、次の形式を使ってください。

インタラクション設定
デフォルトの収束、オーバーシュートなし{ duration: 400, dampingRatio: 1 }
ドラッグ後の位置変更/スナップバック{ duration: 400, dampingRatio: 0.8, velocity }
シート、ドロワー{ duration: 300, dampingRatio: 0.8, velocity }
硬い境界を越えてはならない場合overshootClamping: true を追加

ジェスチャーに勢いがあった場合に限り、バウンスさせます。 フェードインしたメニューがオーバーシュートすると違和感がありますが、フリックしたカードなら自然です。

イージングは、指を使わない動きすべてに使います。

状況イージング
入場または退場ease-out
画面上での移動/変形ease-in-out
一定の動き(進行状況、マーキー)linear
デフォルトease-out

UI では ease-in を決して使わないでください。 動き出しが遅く、ユーザーが注目している瞬間を遅らせます。Reanimated の組み込みイージングは CSS と同じく弱いため、次を使ってください。

import { cubicBezier, Easing } from 'react-native-reanimated';

// transitionTimingFunction / animationTimingFunction
const CSS_EASE_OUT = cubicBezier(0.23, 1, 0.32, 1);

// withTiming / .easing(...)
const EASE_OUT = Easing.bezier(0.23, 1, 0.32, 1);      // strong ease-out for UI
const EASE_IN_OUT = Easing.bezier(0.77, 0, 0.175, 1);  // on-screen movement
const EASE_SHEET = Easing.bezier(0.32, 0.72, 0, 1);    // iOS sheet curve

Reanimated 4.1.1 と 4.5.1 では、生の 'cubic-bezier(...)' 文字列は受け付けられません。CSS のトランジションとアニメーションでは cubicBezier(...) を、withTiming と .easing(...) では Easing.bezier(...) を使います。

時間:

要素時間
押下時のフィードバック100–150ms
トグル、チップ、小さな状態変化150–200ms
シート、モーダル、ドロワースプリング、体感で約300ms
画面遷移プラットフォームのデフォルト。上書きしないでください

モバイル UI のアニメーションは、ウェブと同様に300ms未満にします。プラットフォーム独自のトランジションはより長く(iOS のプッシュ遷移は350ms)、ナビゲーションではプラットフォームに合わせ、それ以外では常にそれを上回る速さにします。

6. JS スレッドから切り離す

これはモバイル特有の技であり、React Native のモーションが最もよく機能しなくなる箇所です。

  • ジェスチャーハンドラーやスクロールハンドラーから setState を決して呼び出さないでください。 フレームごとに React のレンダーが1回発生することが、RN アプリでカクつきが起きる最大の原因です。共有値から useAnimatedStyle を使えば、React の再レンダーは一切発生しません。
  • onUpdate やスクロールハンドラー内で RN ランタイムへの呼び出しを予約しないでください。 react-native-worklets の scheduleOnRN(fn, ...args) は、非推奨の runOnJS(fn)(...args) に代わる Reanimated 4 の機能です。RN ランタイムへの呼び出しをキューに追加しますが、onUpdate 内では毎秒60~120回も呼び出されます。これは onEnd 内、または値がしきい値を超えたときに実行される useAnimatedReaction 内で使ってください。
  • レンダー中に共有値を読み取らないでください(JSX 内の translateY.get() など)。これは更新されないスナップショットであり、気づかないうちに同期がずれます。レンダー中に書き込むことも決してしないでください。 再調整の途中で実行され、意図しない再レンダーによって書き込みが再実行されます。共有値に触れるのは、ワークレット、ハンドラー、エフェクトの中だけにしてください。
  • .value ではなく .get()/.set() を使ってください。 API は同じですが、.value に直接アクセスする形式は React Compiler が解析できません。Reanimated のドキュメントでは get/set がコンパイラーセーフな方法とされています。また、set では関数による更新も使えます。sv.set((v) => v + 1)。
  • ワークレットから呼び出す関数では、先頭行に 'worklet' が必要です。 ない場合、デバッガーでは正常に動作しても、デバイス上では実行時にエラーが発生します。
7. ホバーではなく、押下

Web のホバー操作はすべて、移植するのではなく再設計する必要があります。

  • 押し始めでフィードバックし、押し終わりで確定します。 タップが完了するまで何も表示されないと、反応がないように感じます。これが、ユーザーが実際に感じる遅延です。
  • ボタンのように押せる要素には、Pressable と CSS トランジションを使って、**100~150msでscale: 0.97**にします。scaleはラベルやアイコンも一緒に動かすため、物理的な感触が生まれます。全幅のリスト行は例外で、背景色をハイライトします。行全体を拡大縮小すると、画面全体が縮むように感じられます。
  • 最小タッチ領域は44×44pt(Androidでは48dp)にします。見た目がそれより小さい場合は、見た目を大きくせず、hitSlopを追加します。
  • **pressRetentionOffset**を設定し、指が数ピクセルずれても、意図した押下がキャンセルされないようにします。
  • Android のリップルは、Material スタイルのアプリでのみ使います。 独自デザインのアプリでは、一方のプラットフォームだけにリップルを使うより、両方のプラットフォームで同じ拡大縮小を使うほうが一貫性があります。
8. ハプティクス

モバイルには、Webにはない感覚があります。控えめに使えばアプリに高級感を与えますが、どこでも使うとユーザーはオフにしてしまいます。

場面呼び出し
値が刻み幅を通過する — ピッカー、スライダーのデテント、セグメントコントロールHaptics.selectionAsync()
何かが元の位置にスナップする、シートのデテントに引っかかる、ドラッグが確定するHaptics.impactAsync(ImpactFeedbackStyle.Light)
重いオブジェクトが着地する、破壊的な操作が実行されるHaptics.impactAsync(ImpactFeedbackStyle.Medium)
操作が成功または失敗するHaptics.notificationAsync(NotificationFeedbackType.Success / Error)

守るべきルールは3つで、例外はありません。

  • 視覚的な変化と同じフレームで発生させます。 アニメーションより遅れるハプティクスは、フィードバックではなく不具合のように感じられます。アニメーションが終わったときではなく、デテントに引っかかる瞬間など、原因となるタイミングで発生させます。
  • ユーザー操作1回につき1回です。 スクロール中やフレームごとには発生させず、ユーザーが起こしていない開始アニメーションでも発生させません。
  • ハプティクスだけに頼ってはいけません。 多くのユーザーはシステム全体でハプティクスをオフにしており、Android の多くのデバイスでは音も鳴りません。視覚的な表示だけでも意味が伝わる必要があります。

worklet からハプティクスを呼び出す場合は、RN ランタイムにスケジュールする必要があります:scheduleOnRN(Haptics.selectionAsync)。

9. モーション軽減とアクセシビリティ
import { useReducedMotion, ReduceMotion, withSpring } from 'react-native-reanimated';

const reduced = useReducedMotion();
const y = useSharedValue(reduced ? 0 : SHEET_HEIGHT);

// or let each animation decide
withSpring(0, { duration: 300, dampingRatio: 0.8, reduceMotion: ReduceMotion.System });

モーション軽減は、ゼロにするのではなく、より少なく、より穏やかにすることです。状態変化を説明する不透明度や色の変化は残し、移動、拡大縮小、視差、オーバーシュートは省きます。画面遷移はanimation: 'fade'にします。

テキストは拡大されます。 allowFontScalingはデフォルトで有効なため、デフォルトの文字サイズで測定した高さは、200%では正しくありません。ハードコードした高さにアニメーションさせてはいけません。onLayoutで測定するか、代わりにトランスフォームをアニメーションさせます。

モーションを気づかないうちに壊す設定

「アニメーションがまったく動かない」場合は、まず次の項目を確認してください。

  • SDK とバージョンを一致させるため、Expo 経由でインストールします:npx expo install react-native-reanimated react-native-worklets。Expo プロジェクトでは、babel-preset-expoが worklets Babel プラグインを自動的に設定するため、babel.config.jsでの手順は不要です。このプラグインを手動で追加するのは、そのプリセットを使わない bare RN プロジェクトのみで、その場合はリストの最後に配置する必要があります。プラグインがない、または配置が誤っている場合、現在はサイレントにフォールバックせず、実行時にFailed to create a workletがスローされます。
  • アプリをGestureHandlerRootViewで囲む必要があります。囲まれていない場合、エラーなしでジェスチャーが何も機能しません。
  • Reanimated 4 には New Architecture が必要です。
  • Expo Go はパフォーマンスを評価する環境ではありません。 リリースビルドで操作感を評価してください。開発ビルドでは JS スレッドが遅く、確認したい問題が隠れてしまいます。

120fps

ProMotion 対応の iPhone では、CADisableMinimumFrameDurationOnPhoneを設定しない限り、サードパーティ製のアニメーションは60fpsに制限されます。最近の Expo SDK ではデフォルトで設定されています。設定されていることを確認し、ない場合は追加してください。

{ "expo": { "ios": { "infoPlist": { "CADisableMinimumFrameDurationOnPhone": true } } } }

これにより、フレームの予算は16msではなく8msになります。このため、WebよりもモバイルでUIスレッド上のアニメーションが重要になります。

レシピ

すぐに実装できるパターン(押下フィードバック、ドラッグで閉じるシート、スワイプで削除、折りたたみヘッダー、リストの表示アニメーション、キーボードに同期する UI、タブインジケーター、画面遷移)については、RECIPES.md を参照してください。リクエストがいずれかに該当する場合は、該当するレシピを読み込み、白紙のファイルからではなくレシピをベースにしてください。

リリース禁止

禁止事項代わりに使用
PanRespondergesture-handler の Gesture.Pan()
ジェスチャーやスクロールのハンドラー内での setStateshared value + useAnimatedStyle
runOnJS(Reanimated 4 では非推奨)react-native-worklets の scheduleOnRN
フレームごとの scheduleOnRNonEnd、またはしきい値での useAnimatedReaction
レンダリング中の shared value の読み取りや書き込みworklet、ハンドラー、エフェクト内での .get() / .set()
指で操作する要素に対する Core AnimatedReanimated
height / width / margin / flex / top のアニメーションtransform + opacity(絶対配置で子要素のない要素は除く)
BlurView の強度や Android の elevation のアニメーション静的なレイヤーをクロスフェード
仮想化リストの行への enteringコンテナーをアニメーションするか、itemLayoutAnimation を使用
JS で再構築した画面遷移ネイティブスタックの animation
タブ間のスライドanimation: 'none'
UI 要素での Easing.in(...)Easing.bezier(0.23, 1, 0.32, 1)
Reanimated の CSS スタイル内での 'cubic-bezier(...)'react-native-reanimated の cubicBezier(...)
scale(0) による表示アニメーションscale(0.95) + opacity: 0
距離だけで決まる閉じるしきい値速度 または 距離 — フリックだけで十分です
境界での急停止ラバーバンド抵抗
フレームごとのハプティクス、またはハプティクスだけのフィードバックコミットごとに 1 回、必ず視覚的なフィードバックと併用
Expo Go やシミュレーターでの操作感の評価リリースビルド、サポート対象の中で最も遅いデバイス

出力

コードを書いてください。その後、数行以内で次の内容を示してください。

  • ゲートの判定 — 頻度の分類と目的の名称。却下した内容とその理由も述べてください。
  • 構成要素 — ツール、プロパティ、スプリングまたはカーブ + 時間、スレッド。
  • デバイスで確認する操作感 — ジェスチャー、速度の受け渡し、ハプティクスのタイミングはコードだけでは判断できません。試す操作を挙げてください。フリックする、動作中に割り込む、反転させる、手元で最も遅い Android で実行する、などです。

成果物はコードです。報告書のように冗長にしないでください。

トーン

意見を明確にし、簡潔にしてください。正直な答えが「これはアニメーションさせるべきではありません」や「適切かどうかを判断するには実機が必要です」であれば、そのまま伝えてください。

フィードバックの送信

このスキルにエラー、誤解を招く情報、または古くなった情報があれば、Expo の改善に役立てるため報告してください。

npx --yes @expo/agent-cli@latest feedback --category skills --subject "expo-animation" "<actionable feedback>"

具体的で実行可能な報告内容がある場合にのみ送信してください。関連する背景情報をできるだけ含めてください。 エージェントが Expo のタスクを何度も失敗した場合、またはユーザーが介入する必要があった場合は、タスクの概要、期待される結果、問題点を簡潔にまとめ、--category evals と関連する Expo のパッケージまたはコマンドを --subject として報告してください。

コメント