react-springでアニメーション

Paul Henschel 氏のReact向けアニメーションライブラリ”react-spring”の備忘録です。
https://www.react-spring.io/

version8よりHooks APIが搭載されたので、そちらでの解説になります。
また、React NativeではなくReactでの使用を前提としています。

react-springとは

ReactにSpring Physicsなアニメーション処理を提供するライブラリです。
animatedreact-motionに影響を受けていて、それぞれのアプローチを混ぜ合わせたようなライブラリになっています。

多くの場合、アニメーションはeaseやdurationなどを設定して動かしますが、自然な動きではありません。
react-springは物理的な要素を取り入れ、より自然なアニメーションを目指すようなモチベーションがあります。
一応、easeやdurationを指定したアニメーションも可能です。

インストール

shell
yarn add react-spring

react-springはTypeScriptをサポートしています(3.53時点)が、一部の型エラーが次バージョン以降の修正となります。

基本

指定された値に向けてアニメーションするuseSpringを使用してみます。
useSpringanimatedをimportした、下記Fooコンポーネントを作成します。

src/components/Foo.tsx
import * as React from "react";
import { useSpring, animated } from "react-spring";

const Foo = () => {
  // useSpringでopacityのspringを定義
  const props = useSpring({
    opacity: 1,
    from: { opacity: 0 }
  });
  return (
    <animated.div
      /* styleで操作 */
      style={{ ...props, backgroundColor: "#000", width: 300, height: 300 }}
      role="presentation"
    />
  );
};

export default Foo;

上記のコードでは、fromに指定したopacity: 0が、1になっていく動作を行います。
このアニメーションはコンポーネントではなくDOM側に適用され、renderの更新は行われません。
(springしてる感があまり伝わらなさそうなため、画像はtransformの変更処理に変更)

useSpringで丸が軽快に出てくるアニメーション

コンポーネントなどをアニメーションの対象にする場合は、animatedの引数として指定します。

src/components/Foo.tsx
// styled-componentsで作成
const StyledDiv = styled.div`
  background-color: #000;
  width: 300px;
  height: 300px;
`;

// AnimatedDivを作成
const AnimatedDiv = animated(StyledDiv);

また、propsやstateの値で動的に切り替えることもできます。

src/components/Foo.tsx
// 〜〜略〜〜
const [isActive, setIsActive] = React.useState(false);
const spring = useSpring({
    opacity: isActive ? 1 : 0,
  });
// 〜〜略〜〜

値について

数値から文字列、SVGパスからカラーコードまで、広い範囲で対応しているようです。
公式ドキュメントに使用可能パラメーターの記載があります。

アニメーションの設定

アニメーション向けのconfigがあります。
configはuseSpring内で次のように設定します。

src/components/Foo.tsx
// 〜〜略〜〜
const props = useSpring({
  opacity: 1,
  from: { opacity: 0 },
  config: {
    mass
  }
});
// 〜〜略〜〜

設定は下記の通りです。

  • math: number ... 質量
  • tension: number ... 張力
  • friction: number ... 摩擦力
  • velocity: number ... 速度
  • clamp: boolean ... trueにすると、境界となる値を越えなくなる(バウンドせず止まる)
  • precision: number ... 精度
  • duration: number ... 指定すると、springではなくdurationでアニメーションする
  • easing: t => t ... Easing関数。d3-easeなどを使用可

tensionの値が高いと振り幅が大きくなり、frictionの値が高ければ減衰が早くなります。
公式ドキュメント内でmath, tension, frictionの3要素をシミュレートできます。
https://www.react-spring.io/docs/hooks/api

また、いくつかプリセットファイルも用意されていて、configをimportすることで使えるようになります。

src/components/Foo.tsx
import { useSpring, animated, config } from 'react-spring';

〜〜〜
config: config.wobbly,

interpolation

interpolation関数を使うこともできます。

下記は、useSpringを用いて0から1になる変数oを定義しています。

src/components/Foo.tsx
const { o } = useSpring({
  o: 1,
  from: { o: 0 },
});

interpolate関数を用いて、toFixed()の値を返すことで、変数oの値が0から1まで増加する様子が、文字列となって出力されます。

src/components/Foo.tsx
import * as React from 'react';
import { useSpring, animated } from 'react-spring';

const Foo = () => {
  const { o } = useSpring({
    o: 1,
    from: { o: 0 },
  });
  return (
    <>
      <animated.p>{o.interpolate(n => n.toFixed(2))}</animated.p>
    </>
  );
};

export default Foo;
0から1の間の数字が表示されるアニメーション

interpolate関数を使えば、様々な属性、パラメータに、異なる補正をかけた処理が適用できます。
この値を使って、animated.div要素にtransform: scale()を適用してみます。

その際に、ちょっとした補間処理を挟んでみます。
interpolateに、rangeoutputプロパティの入ったオブジェクトを渡しました。
そして、rangeプロパティに、0 〜 1までの範囲で数値を5つ設定しています。

src/components/Foo.tsx
<animated.div
  style={{
    transform: 
        o.interpolate({
          range: [0, 0.25, 0.5, 0.75, 1],
          output: [1, 0.5, 0.2, 0.5, 1]
        })
      .interpolate(o => `scale(${o})`)
  }}
/>;

この場合のrangeoutputの関係は、CSSにおけるkeyframesに近いです。
例えば進行度が0.25(25%)の時に、scaleの値は0.5になる、となる見方です。
それが1→0.5→0.2→0.5→1と並ぶので、一度小さくなってから元の大きさに戻るアニメーションになります。

黒い丸が一旦小さくなるアニメーション

ちなみにrangeoutputを使用する場合は、次のようなショートカットが使えます。

src/components/Foo.tsx
<animated.div
  style={{
    transform: 
      //@ts-ignore
      o.interpolate([0, 0.25, 0.5, 0.75, 1],  [1, 0.5, 0.2, 0.5, 1])
      .interpolate(o => `scale(${o})`)
  }}
/>;

TypeScriptでは型エラーが出ますが、v9で修正済みです。
v8での修正予定はありません(won't fixになっています)。

APIの種類

useSpringを基本として、5種類のAPIが出ています。
上手く使えば複数の要素にspringアニメーションを適用したり、アニメーションの順序を制御することができるようです。

useSpring

先ほどからやっているものです。

useSprings

useSpringの複数形で、springのデータが配列として入ります。
indexをとってdelayをかけることもできますが、そうした処理をする場合は次項のuseTrailが使用できます。

useTrail

useSpringsとほぼ同じですが、順番にspringを実行します。
下GIFでは、1文字ずつ区切って配列にしたものにuseTrailを適用しています。

useTrailの文字が順にくるくる回るアニメーション

useTransition

要素のmount / unmountを検知してアニメーションを実行します。
mapで生成した複数のコンポーネントのアニメーションや、enter / leaveアニメーションに便利です。
下GIFでは、4つのコンポーネントを順番に切り替えています。

スライドショーのように切りかわるコンポーネントのアニメーション

useChain

複数のrefを含んだアニメーションを配列に渡して、アニメーションの順番を制御します。
下GIFでは、ドロワー部分をuseSpringで表示させた後に、リストコンポーネントをuseTransitionでアニメーションさせています。

スライドショーのように切りかわるコンポーネントのアニメーション

おわり

難しい設定や秘伝のタレや職人技なしに、自然なアニメーションを作成することができました。
ちょっとしたインタラクションからページ遷移のような仕組みまで実装できて、汎用性が高いです。
また、react-use-gestureを組み合わせれば、ネイティブライクな操作性などを楽に実装することができます。

問題点も結構あるのですが、一旦ここまでにします。