アクセシビリティ
「動きを減らす」設定にアニメーションを合わせるための細かなふるまいと、スクリーンリーダーなどアニメーション以外の配慮をまとめます。実際に動くデモは第3部3章にあります。
アニメーションは体験を豊かにする一方で、人によっては使いづらさの原因にもなります。動きに敏感なユーザーは、大きく揺れる画面で目まいを感じることがありますし、スクリーンリーダーを使うユーザーには、視覚的な動きそのものが情報として届きません。この章では、Reanimatedのアニメーションをアクセシビリティに配慮させるための細かなふるまいと、動き以外の配慮点を整理します。
「動きを減らす」設定への対応の基本と、実際に切り替わるデモは第3部3章で扱いました。useReducedMotionフックとReducedMotionConfigコンポーネントの使い方はそちらに任せ、この章ではもう一段細かいふるまいと、スクリーンリーダーへの配慮を補います。

既定のSystemは端末設定に従い、AlwaysとNeverはアニメーションごとに動きの扱いを固定します。
ReduceMotionの3つの値
個々のアニメーションには、reduceMotionオプションで「動きを減らす」設定への従い方を指定できます。値はReduceMotion列挙型の3つです。
ReduceMotion.System— デバイスの設定に従う。設定が有効ならアニメーションを無効化し、無効なら通常どおり動かすReduceMotion.Always— デバイスの設定に関わらず、常に無効化するReduceMotion.Never— 常に有効のまま動かす
既定ではすべてのアニメーションがReduceMotion.Systemで構成されています。つまり、何も指定しなければ自動的にデバイスの設定を尊重します。
import { withDelay, withTiming, ReduceMotion } from 'react-native-reanimated';
function App() {
sv1.value = withTiming(0, { reduceMotion: ReduceMotion.System });
sv2.value = withDelay(1000, withTiming(toValue, { duration }), ReduceMotion.System);
// ...
}
無効化されたときのふるまい
「動きを減らす」が有効なとき、各アニメーション関数は動きを再生する代わりに、目的の状態へ即座に到達します。ただし関数ごとにふるまいが少しずつ違います。
withSpringとwithTimingは、toValueを即座に返すwithDecayは、クランプの設定を考慮したうえで現在の値を即座に返すwithDelayは、遅延を挟まず次のアニメーションをすぐ開始するwithRepeatは、繰り返し回数が無限または偶数で、かつ動きが反転する構成のときは繰り返し自体を開始しない。それ以外のときは1回だけ実行するwithSequenceは、動きを減らす設定が無効になっているアニメーションだけを再生する
高階のアニメーションと設定の継承
withDelayやwithSequenceのような高階のアニメーションは、自分のreduceMotion設定を子のアニメーションへ引き継ぎます。ただし引き継ぐのは、子がユーザーによって設定されていない場合だけです。子が自分でreduceMotionを持っていれば、そちらが優先されます。
たとえば次のアニメーションは、外側のwithDelayがAlwaysを指定しているので、withTimingはtoValueへ瞬時に到達します。
sv.value = withDelay(1000, withTiming(toValue, { duration }), ReduceMotion.Always);
逆にこちらはNeverなので、デバイスで「動きを減らす」が有効でも通常どおり動きます。
sv.value = withDelay(1000, withTiming(toValue, { duration }), ReduceMotion.Never);
そして次の例では、子のwithTimingが自分でNeverを持っているため、外側のAlwaysを上書きします。結果としてwithTimingは通常どおり、しかも遅延なしで実行されます。
sv.value = withDelay(
1000,
withTiming(toValue, { duration, reduceMotion: ReduceMotion.Never }),
ReduceMotion.Always
);
レイアウトアニメーションでの指定
レイアウトアニメーションにも、.reduceMotion()メソッドで同じ設定を渡せます。
import { BounceIn, ReduceMotion } from 'react-native-reanimated';
function App() {
const entering = BounceIn.reduceMotion(ReduceMotion.System);
// ...
}
「動きを減らす」が有効なときのふるまいは次のとおりです。
- 出現(entering)・キーフレーム・レイアウトアニメーションは、終点へ瞬時に到達する
- 消滅(exiting)アニメーションと共有要素トランジションは省略される
useReducedMotionと組み合わせれば、設定に応じてアニメーション自体を差し替えることもできます。派手なBounceInの代わりに控えめなFadeInを使う、といった切り替えです。
import { BounceIn, FadeIn, ReduceMotion } from 'react-native-reanimated';
function App() {
const reduceMotion = useReducedMotion();
const entering = reduceMotion ? FadeIn.reduceMotion(ReduceMotion.Never) : BounceIn;
// ...
}
スクリーンリーダーへの配慮
「動きを減らす」への対応は、アクセシビリティの一部でしかありません。スクリーンリーダーを使うユーザーには、そもそも視覚的な動きが情報として届かない、という前提を忘れないようにしたいところです。
アニメーションで状態の変化を伝えているなら、その情報が動き以外の手段でも届くようにします。たとえば要素がスライドインして「新着あり」を示すなら、その要素にaccessibilityLabelで内容を持たせ、必要ならAccessibilityInfo.announceForAccessibilityで変化を読み上げます。動きだけに意味を負わせないのが基本です。
アニメーション中の中間状態が読み上げられて邪魔になる場合は、アニメーションの入れ物にaccessibilityElementsHidden(iOS)やimportantForAccessibility="no-hide-descendants"(Android)を使い、動いている最中の要素をアクセシビリティツリーから外す、という選択肢もあります。フォーカスの制御が必要なときは、React NativeのAccessibilityInfoが持つAPIを組み合わせてください。
これらはReanimated固有の機能ではなく、React Nativeのアクセシビリティ機構の話です。アニメーションを足すときには、見えている動きの裏で、見えない経路の情報が失われていないかをあわせて確認する、と覚えておくとよいと思います。