スクロール連動アニメーション
useAnimatedScrollHandler・useScrollOffset・scrollToを使い、スクロール位置に連動する動きと、プログラムからのスクロールを組み立てます。
前章のジェスチャーと並んで、もう一つよく使う「生きた値」がスクロール位置です。リストをどこまでスクロールしたかに応じてヘッダーを縮めたり、プログレスバーを伸ばしたり。こうした表現は、スクロール量を共有値として受け取り、そこからスタイルを組み立てることで実現します。
Reanimatedはスクロールを扱う3つの道具を用意しています。スクロールイベントを受け取るuseAnimatedScrollHandler、現在のオフセットを共有値として取り出すuseScrollOffset、そしてコードからスクロールを動かすscrollToです。順に見ていきます。

スクロール位置は共有値として見た目へ流れ、scrollToは反対向きにスクロール位置を操作します。
スクロールイベントを受け取る
useAnimatedScrollHandlerは、スクロールイベント用のハンドラーを返すフックです。React Nativeのスクロール可能なコンポーネントに接続して使います。
const offsetY = useSharedValue(0);
const scrollHandler = useAnimatedScrollHandler((event) => {
offsetY.value = event.contentOffset.y;
});
返ってきたハンドラーは、onScrollプロパティに渡します。このとき、接続する側はAnimatedでラップされたコンテナでなければなりません。ただのScrollViewではなくAnimated.ScrollViewを使う、という点に注意してください。
<Animated.ScrollView onScroll={scrollHandler}>{/* ... */}</Animated.ScrollView>
引数のhandlersには、ネイティブのイベント名に対応するキーを持つオブジェクトを渡せます。使えるキーはonScroll・onBeginDrag・onEndDrag・onMomentumBegin・onMomentumEndの5つです。それぞれの値は個別のworkletで、対応するイベントが発火したときに呼ばれます。
上のコードのように、オブジェクトではなく単一のworklet((event) => void)を渡した場合は、それがonScrollのハンドラーとして扱われます。
各workletはeventとcontextを受け取ります。eventはスクロール情報を持つオブジェクトで、そのcontentOffset.yから縦方向のスクロール量が読めます。contextはイベントをまたいで状態を保持できるプレーンなJSオブジェクトで、複数のハンドラーを登録したときは、それらの間で共有されます。
第2引数のdependenciesは省略可能です。これが関係するのは、Babelプラグインを使わないWeb環境だけです。本書のデモもこの構成なので、workletが読む共有値を依存配列に明示しています。
次のデモは、受け取ったスクロール量をuseAnimatedStyleに流し込み、スクロールに合わせてヘッダーの高さと色を変えています。下へスクロールするとヘッダーが縮みます。
スクロール量を共有値として取り出す
イベントを自分で受け取らず、現在のオフセットだけがほしいこともあります。そのときはuseScrollOffsetが便利です。スクロール可能なコンポーネントのオフセットを、そのまま共有値として返してくれます。
const animatedRef = useAnimatedRef<ScrollView>();
const scrollOffset = useScrollOffset(animatedRef);
引数には、スクロール要素に接続したアニメーテッドrefを渡します。このrefは、AnimatedコンポーネントかReact Nativeの組み込みコンポーネントに渡す必要があります。フックはスクロールが縦か横かを自動で判別するので、向きを気にせず使えます。
第2引数のprovidedOffsetは省略可能で、更新先の共有値を自分で渡したいときに使います。省略すると、内部で作られた共有値が返ります。戻り値は、現在のオフセットを保持する共有値です。
次のデモは、取り出したオフセットをinterpolateでプログレスバーの幅に変換しています。スクロールに合わせてバーが伸び縮みします。
useScrollOffsetはScrollViewだけでなくFlatListやFlashListでも使えます。また、渡すrefはあとから差し替えても、フックは接続先に応じた値を正しく返します。
プログラムからスクロールする
ここまではユーザーのスクロールに反応する側でしたが、逆にコードからスクロールを動かしたい場面もあります。scrollToは、指定したXまたはYのオフセットへ同期的にスクロールします。
const animatedRef = useAnimatedRef();
const scroll = useSharedValue(0);
useDerivedValue(() => {
scrollTo(animatedRef, 0, scroll.value, true);
});
引数は4つです。animatedRefはスクロール要素に接続したアニメーテッドref、xは横方向、yは縦方向のスクロール先(いずれもピクセル)、そしてanimatedはスクロールをなめらかにするか(true)、一瞬で移動するか(false)を決めます。戻り値はありません。
次のデモは、ボタンを押すと共有値scrollが変わり、それを監視するuseDerivedValueの中でscrollToが呼ばれ、対応する位置までスクロールします。
scrollToはAnimated.FlatListにも対応します。内部でScrollViewを使っていてAnimated化されたコンポーネントであれば、たいてい動きます。
この章のまとめ
useAnimatedScrollHandlerはスクロールイベント用のハンドラーを返します。Animated.ScrollViewのonScrollに渡して使いますuseScrollOffsetは、スクロール要素の現在のオフセットを共有値として返します。向きは自動で判別されますscrollToはコードからスクロール位置を動かします。UIスレッドから呼び出す必要があります
スクロール位置もジェスチャーと同じく、共有値を経由してスタイルへ流し込むのが基本の形です。次章では、キーボードやセンサーといったデバイス由来の値、そしてアクセシビリティ設定への対応を扱います。