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

要素の測定と参照

useAnimatedRefで要素への参照を取り、measureで画面上の寸法と位置をUIスレッドで測る方法を扱います。dispatchCommand・setNativeProps・getRelativeCoordsといった命令的な操作もあわせて見ていきます。

これまでのアニメーションは、共有値やスタイルを通じて「宣言的」に要素を動かしてきました。値が変われば、Reanimatedがそれに追従して描画してくれる。ほとんどの場面はこれで足ります。

一方で、「いま画面上でこの要素はどの位置にいて、どのくらいの大きさなのか」を実際に測りたい場面があります。ドロップダウンを要素の真下に出す、要素の位置に合わせて別のものを動かす、といったケースです。この章は、そうした測定と、要素に直接命令を出す命令的なAPIをまとめて扱います。いずれも第6部1章で見た「UIスレッド」「UIランタイム」の話が土台になります。

useAnimatedRef — 要素への参照

すべての起点はuseAnimatedRefです。これは要素への参照を取るフックで、measurescrollTouseScrollOffsetと組み合わせて使います。使い方は普通のrefと同じで、返ってきたオブジェクトをrefプロパティに渡すだけです。

import Animated, { useAnimatedRef } from "react-native-reanimated";

function App() {
  const animatedRef = useAnimatedRef();

  return <Animated.View ref={animatedRef} />;
}

useAnimatedRefはAnimated版のコンポーネントに限らず、素のReact Nativeコンポーネントにも使えます。中身のcurrentはコンポーネントがマウントされたあとに入るので、レンダー中に読むとnullです。読むならエフェクトやイベントハンドラーの中で、というのは通常のrefと同じ注意点です。

普通のrefとの違いは、この参照が測定系のworkletから使える形になっている点にあります。次のmeasureがまさにそれです。

measure — 画面上の寸法と位置を測る

measureは、要素の寸法と画面上の位置を同期的に、UIスレッドで取得します。返ってくるのは次の6つの値です。

画面基準のpageXとpageY、親要素基準のxとy、要素のwidthとheightを示す座標図

measureは寸法と2種類の座標を返し、getRelativeCoordsは画面上の点を対象要素の座標系へ変換します。

  • x, y: 親コンポーネントを基準にした座標
  • width, height: 要素の幅と高さ
  • pageX, pageY: 画面全体を基準にした座標

測定できなかったときはnullを返します。ここが実際のコードで最初につまずくポイントです。refがまだ要素につながっていないときや、画面外の要素を測ろうとしたときはnullになるので、測定後のnullチェックは省略できません。

そしてもうひとつ大事なのが、measureはUIスレッドにしか実装がないことです。ボタンのイベントハンドラー(JSスレッド)から呼ぶには、UIランタイムへ処理を送る必要があります。第6部1章で見たscheduleOnUIの出番です。

import { measure, useAnimatedRef } from "react-native-reanimated";
import { scheduleOnRN, scheduleOnUI } from "react-native-worklets";

function App() {
  const animatedRef = useAnimatedRef();

  const handleMeasure = () => {
    scheduleOnUI(() => {
      "worklet";
      const measurement = measure(animatedRef);
      if (measurement === null) {
        return;
      }
      // ここは UI スレッド。JS 側に結果を戻すなら scheduleOnRN
      scheduleOnRN(setResult, measurement);
    });
  };
}

流れを整理すると、scheduleOnUIでUIランタイムにworkletを積み、その中でmeasureして寸法を得て、JS側で表示したいのでscheduleOnRNで結果を戻す、という往復です。第6部1章で「スレッドをまたいで関数を呼ぶ」と言っていたのは、まさにこういう場面のためのしくみでした。

次のデモは、ボタンで実際に測定した結果を表示します。「幅を変える」で要素のレイアウトを変えてから「測定する」を押すと、measureが現在の画面上の値を返すことが確認できます。デモのコードでは従来名のrunOnUI / runOnJSを使っています。中身はscheduleOnUI / scheduleOnRNと同じで、reanimatedからそのままimportできるためです。

デモを読み込み中…

寸法だけがほしくて、しかもアニメーション中には使わない場合は、measureではなくReact NativeのonLayoutプロパティのほうが手軽です。measureが生きるのは、ジェスチャーやアニメーションの最中に、UIスレッド上で最新の位置を即座に知りたいときです。

getRelativeCoords — 絶対座標を相対座標に変換する

ジェスチャーのイベントは、たいてい画面全体を基準にした絶対座標(absoluteX / absoluteY)で届きます。これを「特定の要素の中での位置」に直したいことがあります。タップした場所に印を置く、といったケースです。

getRelativeCoordsは、useAnimatedRefで参照した要素と絶対座標を渡すと、その要素を基準にした相対座標を返します。

import { getRelativeCoords, useAnimatedRef } from "react-native-reanimated";
import { Gesture, GestureDetector } from "react-native-gesture-handler";

function Comp() {
  const animatedRef = useAnimatedRef();

  const panGesture = Gesture.Pan().onEnd((event) => {
    const coords = getRelativeCoords(
      animatedRef,
      event.absoluteX,
      event.absoluteY
    );
    if (coords) {
      // coords.x と coords.y を使う
    }
  });

  return (
    <GestureDetector gesture={panGesture}>
      <Animated.View ref={animatedRef} style={styles.box} />
    </GestureDetector>
  );
}

内部ではmeasureを使って要素の位置を求め、そこから絶対座標を引く形になっています。測定に失敗すればnullが返るので、やはりnullチェックが必要です。

命令的にプロパティを更新する

ここからは、宣言的なアニメーションから少し外れた、命令的なAPIです。原則として、スタイルやプロパティのアニメーションにはuseAnimatedStyleuseAnimatedPropsを先に検討してください。次の2つは、それでは届かない特定のケースのための逃げ道です。

dispatchCommand

dispatchCommandは、ネイティブコンポーネントのコマンドをUIスレッドから直接実行します。代表的なのはテキスト入力へのフォーカスです。

import { dispatchCommand, useAnimatedRef } from "react-native-reanimated";

function App() {
  const animatedRef = useAnimatedRef();

  const gesture = Gesture.Tap().onStart(() => {
    dispatchCommand(animatedRef, "focus");
  });

  return <AnimatedTextInput ref={animatedRef} />;
}

第2引数のコマンド名('focus''scrollToEnd'など)はコンポーネントごとに異なります。どんなコマンドが使えるかは、対象コンポーネントのReact Nativeのドキュメントを確認してください。第3引数でコマンドの引数を配列で渡せます。

setNativeProps

setNativePropsは、コンポーネントのプロパティを命令的に書き換えます。スタイルプロパティ(widthbackgroundColor)も通常のプロパティ(text)も更新できます。

import { setNativeProps, useAnimatedRef } from "react-native-reanimated";

function App() {
  const animatedRef = useAnimatedRef();

  const tap = Gesture.Tap().onEnd(() => {
    setNativeProps(animatedRef, { text: "" });
  });

  return <TextInput ref={animatedRef} />;
}

これはジェスチャーハンドラーから命令的にプロパティを更新するために作られたAPIで、UIスレッドで使う想定です。ジェスチャー以外の文脈から使うとscheduleOnUIでくるむ手間が増え、そういう場面ではReact Native標準のsetNativePropsのほうが素直に動きます。あくまで例外的なケースのための道具、という位置づけです。

dispatchCommandsetNativePropsはネイティブのビューへ直接コマンドを送る性質上、iOSとAndroidが対象です。webでは意味を持たないため、本書のデモは用意せずコード例にとどめています。

この章のまとめ

要素の測定と命令的な操作は、宣言的なアニメーションだけでは届かない場面のための道具です。useAnimatedRefで参照を取り、measureで画面上の寸法を測る。この2つが基本の組み合わせで、getRelativeCoordsはその応用です。dispatchCommandsetNativePropsは、命令的な更新がどうしても必要なときの逃げ道として頭の隅に置いておけば十分です。

いずれのAPIも、どのスレッドで動くのかを意識すると挙動が読みやすくなります。第6部1章の内容がそのまま効いてくる章でした。

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