Skip to content
React Native Reanimated ガイドブック
Esc
navigateopen⌘Jpreview
On this page

共有要素トランジション

sharedTransitionTagで画面をまたいで要素をなめらかに変形させる共有要素トランジションを扱います。実験的機能としての位置づけ、フィーチャーフラグ、react-navigation前提の制約を正確に伝えます。

これまでの章は、1つの画面の中で要素が現れたり動いたりする話でした。この章で扱う共有要素トランジションは、画面をまたいだ動きです。一覧画面のサムネイルをタップすると、その画像がそのまま拡大しながら詳細画面のヘッダーへ変形していく。あの動きを作るための機能です。

最初に位置づけを正確に伝えておきます。共有要素トランジションは実験的機能で、フィーチャーフラグの裏側にあり、本番利用はまだ推奨されていません。対応プラットフォームもiOSとAndroidのみで、webでは動きません。この章はデモを置かず、しくみと制約を読み物として押さえる構成にします。実際に試すときは、公式のフィーチャーフラグを有効にしたうえでネイティブで動かしてください。

しくみ

使い方はとてもシンプルで、2つの画面にある要素へ同じsharedTransitionTagを付けるだけです。

画面Aの要素を一時的なコピーへ置き換え、画面Bの要素へ変形させる3段階の図

遷移中だけ一時的なコピーが前面を移動し、終わると遷移先の本物の要素へ表示を戻します。

import Animated from "react-native-reanimated";

const Stack = createNativeStackNavigator();

function One({ navigation }) {
  return (
    <>
      <Animated.View sharedTransitionTag="sharedTag" />
      <Button title="Two" onPress={() => navigation.navigate("Two")} />
    </>
  );
}

function Two({ navigation }) {
  return (
    <>
      <Animated.View sharedTransitionTag="sharedTag" />
      <Button title="One" onPress={() => navigation.navigate("One")} />
    </>
  );
}

スタックの一番上の画面が変わると、Reanimatedは新しい画面と古い画面の両方に、同じsharedTransitionTagを持つ要素があるかを調べます。見つかると、両方の要素をいったん透明にして、その上に元の要素とそっくりな3つ目のビューを重ねます。この重ねたビューを、移動先の要素の見た目までアニメーションさせ、終わったら取り除いて両方の要素の透明度を戻す、という流れです。

既定ではwidth / height / originX / originY / transform / backgroundColor / opacityが、withTimingで500msかけてアニメーションします。現時点では、アニメーション関数を完全に自作することはできません。

同じ画面で複数の共有ビューを使う場合は、それぞれに一意なタグを割り当ててください。タグが重複すると、どの要素同士を対応づけるか決まりません。

カスタマイズ

動きの調整はSharedTransitionビルダーで行います。カスタマイズできる範囲は機能の成熟にともなって広がっていく予定で、今のところは再生時間の指定とバネベースへの切り替えができます。

import { SharedTransition } from "react-native-reanimated";

const transition = SharedTransition.duration(550).springify();

function SharedView() {
  return (
    <Animated.View
      sharedTransitionTag="reanimatedTransition"
      sharedTransitionStyle={transition}
      style={{ backgroundColor: "blue", width: 200, height: 100 }}
    />
  );
}

iOSでは、スワイプジェスチャーやOSの戻るボタンでトランジションが始まったとき、時間ベースではなく進行度ベースのトランジションが走ります。これにより、指の動きに合わせてアニメーションが進み、途中でのキャンセルもできます。この挙動も、将来的にはカスタマイズできるようになる予定です。

現時点の制約

実験的機能だけあって、把握しておくべき制約がいくつもあります。公式が挙げているものを整理します。

  • ナビゲーターはネイティブスタックのみ対応。タブナビゲーターは未対応で、共有トランジションを含む経路で使うとアニメーションが走らないことがある
  • iOSでは、ネイティブモーダル(transparentModal)とのトランジションが正しく動かない。モーダルが共有要素を覆ってしまう
  • アニメーション関数を自作することはできない
  • 一部のプロパティ(例: backgroundColor)は進行度ベースのトランジションでは非対応
  • iOSでは、トランジション中のビューの縦位置がずれることがある。ヘッダー高さの情報伝播に起因する
  • transformの再計算が過剰に走るため、パフォーマンス上のボトルネックがある
  • react-native-screensに対して、遷移元画面のスナップショットを保持させないための回避策が必要。修正は3.19以降で利用できる
  • フィーチャーフラグを有効にすると、消滅アニメーションを持つビューのzIndexが変わる(前面に持ち上げられる)。これはReanimated 4の通常の挙動とは異なる
  • fast refresh時に、同じ画面上の要素に対してトランジションが走ってしまうことがある。ナビゲーターがコンテキストプロバイダーで包まれている場合などに起こりうる

これらは今後の実装で改善される見込みですが、現状では本番導入の前によく確認しておく必要があります。凝った画面間トランジションを本格的に使いたい場合は、react-navigation自体のトランジション機能や、専用のライブラリとあわせて検討するのが現実的でしょう。

第4部はここまでです。プリセットからキーフレーム、カスタム、そして画面をまたぐ共有要素まで、Reanimatedがレイアウトの変化を動かす仕組みをひととおり見てきました。次の第5部では、これらとは別系統のCSSアニメーションAPIに進みます。

この章のもとになった公式ドキュメント