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

SVGのアニメーション

react-native-svgの図形を、useAnimatedPropsやCSSアニメーション・トランジションで動かす方法を扱います。本書のデモには載せていないため、コード例と概念の説明が中心です。

Reanimatedはreact-native-svgのコンポーネントをアニメーションできます。円の半径やパスの形といった図形そのもの(cxrdpointsなど)も、塗りや線といった見た目(fillstrokeopacityなど)も対象です。

react-native-svgは本書のデモ環境には導入していないので、この章は動くデモではなくコード例と考え方の説明でお伝えします。実際に手を動かすときは、公式ドキュメントのデモとあわせて読むと分かりやすいはずです。

セットアップ

まずreact-native-svgをインストールします。SVGのコンポーネントはReanimatedに組み込まれていないので、アニメーションさせたいものはcreateAnimatedComponentでラップして対応させます。

import Animated from 'react-native-reanimated';
import { Circle } from 'react-native-svg';

const AnimatedCircle = Animated.createAnimatedComponent(Circle);

このcreateAnimatedComponentは、第6部4章で扱った「Reanimated標準以外のコンポーネントをアニメーション対応にする」仕組みそのものです。

3つの動かし方

cxrdfillといったSVGの属性は、React Nativeのstyleのキーではなくコンポーネントのpropsです。そのため、これらはstyleではなくpropsを通してアニメーションします。動かし方は3通りあります。

  • インライン — 共有値をpropsへ直接渡す(<AnimatedCircle r={r} />)
  • useAnimatedProps — worklet内でpropsを計算し、その結果をanimatedPropsに渡す
  • CSS — SVGのpropsは素の値のまま置き、animationNametransitionPropertyanimatedPropsに加える

useAnimatedProps第2部2章で、CSSアニメーション・トランジションは第5部で扱った書き方が、そのままSVGにも通じます。

useAnimatedPropsで動かす

共有値をuseAnimatedProps経由で属性に流し込む例です。円の半径rを、1秒かけて往復させ続けます。

import { useEffect } from 'react';
import Animated, {
  Easing,
  useAnimatedProps,
  useSharedValue,
  withRepeat,
  withTiming,
} from 'react-native-reanimated';
import { Circle, Svg } from 'react-native-svg';

const AnimatedCircle = Animated.createAnimatedComponent(Circle);

function App() {
  const r = useSharedValue(20);

  useEffect(() => {
    const easing = Easing.bezier(0.25, 0.1, 0.25, 1); // CSSの`ease`に相当
    r.value = withRepeat(withTiming(50, { duration: 1000, easing }), -1, true);
  }, []);

  const animatedProps = useAnimatedProps(() => ({
    r: r.value,
  }));

  return (
    <Svg style={{ height: 100, width: '100%' }}>
      <AnimatedCircle cx="50%" cy="50%" fill="#b58df1" animatedProps={animatedProps} />
    </Svg>
  );
}

共有値を起点に、worklet内で組み立てたpropsをanimatedPropsに渡す。第2部で身につけたコアAPIの型が、そのまま図形の属性に向いているだけです。

CSSアニメーションで動かす

同じ往復を、CSSのキーフレームアニメーションで書くこともできます。共有値もuseAnimatedPropsも使わず、animatedPropsにアニメーションの定義を並べるだけです。

<AnimatedCircle
  cx="50%"
  cy="50%"
  r={20}
  fill="#b58df1"
  animatedProps={{
    animationName: {
      from: { r: 20 },
      to: { r: 50 },
    },
    animationDuration: '1s',
    animationIterationCount: 'infinite',
    animationDirection: 'alternate',
    animationTimingFunction: 'ease',
  }}
/>

CSSトランジションで動かす

CSSトランジションは、対象の値がレンダーの間で変化したときに走ります。useStateやpropsから来る素の値が変われば、トランジションが動きます。

function App() {
  const [grown, setGrown] = useState(false);

  return (
    <>
      <Svg style={{ height: 100, width: '100%' }}>
        <AnimatedCircle
          cx="50%"
          cy="50%"
          fill="#b58df1"
          r={grown ? 50 : 20}
          animatedProps={{
            transitionProperty: 'r',
            transitionDuration: 300,
            transitionTimingFunction: 'ease',
          }}
        />
      </Svg>
      <Button title="Toggle" onPress={() => setGrown((value) => !value)} />
    </>
  );
}

最初のレンダーでは前の値がないのでアニメーションしません。以降、rが変わるたびに古い値から新しい値へトランジションします。

ここで一つ注意点があります。共有値だけは例外で、r.valueを変えても再レンダーが起きないため、トランジションは始まりません。共有値からpropを動かしたいときは、インラインでr={r}と渡すか、useAnimatedPropsを使ってください。

パスのモーフィング

Pathは、2つの形が同じコマンドの並びで書かれていれば、その間をなめらかに変形(モーフィング)できます。dは本物のCSSプロパティに対応するので、これはWeb上でも動きます。

import Animated from 'react-native-reanimated';
import { Path, Svg } from 'react-native-svg';

const AnimatedPath = Animated.createAnimatedComponent(Path);

const CIRCLE =
  'M50 10 C72 10 90 28 90 50 C90 72 72 90 50 90 C28 90 10 72 10 50 C10 28 28 10 50 10 Z';
const STAR =
  'M50 10 C50 22 78 50 90 50 C78 50 50 78 50 90 C50 78 22 50 10 50 C22 50 50 22 50 10 Z';

function App() {
  return (
    <Svg height={160} width="100%" viewBox="0 0 100 100">
      <AnimatedPath
        fill="#b58df1"
        d={CIRCLE}
        animatedProps={{
          animationName: { from: { d: CIRCLE }, to: { d: STAR } },
          animationDuration: '2s',
          animationIterationCount: 'infinite',
          animationDirection: 'alternate',
          animationTimingFunction: 'ease-in-out',
        }}
      />
    </Svg>
  );
}

iOSとAndroidでは、d(Path)とpoints(Polygon・Polyline)はどんな形の間でも自由にモーフィングします。WebではPathだけが対象で、しかもコマンド構造が一致するパス同士でないと、途中を補間せずにパッと切り替わります。

アニメーションできるコンポーネントとプロパティ

ここからは、CSSアニメーション・トランジションで何を、どのプラットフォームで動かせるかの一覧です。すべてのコンポーネントが共通の見た目プロパティをアニメーションでき、図形コンポーネントはそれに加えて自分の図形プロパティも動かせます。一覧にないプロパティはCSSではサポートされていないので、その場合はuseAnimatedPropsを使ってください。useAnimatedPropsは、コンポーネントが受け取るアニメーション可能な任意のpropを動かせます。

共通の見た目プロパティ

すべてのreact-native-svgコンポーネントが次のpropsを持つため、どのコンポーネント上でもアニメーションできます。

プロパティ Android iOS Web
color
fill
fillOpacity
fillRule
stroke
strokeWidth
strokeOpacity
strokeDasharray
strokeDashoffset
strokeLinecap
strokeLinejoin
vectorEffect
opacity
pointerEvents
clipPath
clipRule
mask
filter
marker

コンポーネントごとの図形プロパティ

次のコンポーネントは、iOSとAndroidで自分の図形プロパティをアニメーションできます。Web列は、その図形がWebでも動くかどうかを表します。

コンポーネント 図形プロパティ Web
Circle cx, cy, r
Ellipse cx, cy, rx, ry
Rect x, y, width, height, rx, ry
Image x, y, width, height
Path d ✅¹
Polygon, Polyline points
Line x1, y1, x2, y2
Text x, y, dx, dy, rotate
Pattern x, y, width, height, patternUnits, patternContentUnits
LinearGradient x1, y1, x2, y2, gradient, gradientUnits
RadialGradient cx, cy, r, rx, ry, fx, fy, gradient, gradientUnits

¹ Webでは、Pathdはコマンド構造が一致するパスの間だけモーフィングし、不一致のときはパッと切り替わります。iOSとAndroidは自由にモーフィングします。

残りのコンポーネント(GUseSymbolDefsClipPathMaskMarkerTSpanTextPathForeignObject)は、アニメーションできる図形プロパティを持たず、共通の見た目プロパティだけを動かせます。Webでは、このなかでGのみサポートされます。

PatternxyはiOS専用です(react-native-svgがアニメーション以前にAndroidで対応していません)。Textxydxdyrotateは、グリフごとの配列も受け取れます。

補足

モーフィングと単位の注意点

d(Path)とpoints(Polygon・Polyline)は、iOSとAndroidではどんな形の間でも自由にモーフィングします。WebではPathだけが対象で、PolygonPolylinepointsはまったくアニメーションしません。react-native-svgがこれらをネイティブの<polygon><polyline>として描画し、そのpointsはCSSプロパティではないためです。

図形プロパティ(cxrxwidthなど)は、素の数値かパーセント文字列('50%')を受け取り、1つのアニメーションのなかで両者を混ぜることもできます。ただしグラデーションとパターンの座標だけは例外で、パーセント文字列と01の割合は互いに補間できないので、1つのアニメーションではどちらか一方に統一してください。

段階的に切り替わるプロパティ

一部のプロパティは、値を補間せずに離散的な値の間で切り替わります。strokeLinecapstrokeLinejoinfillRulevectorEffectgradientUnitspatternUnitsが該当します。これはネイティブのSVGやCSSの挙動に合わせたものです。

グラデーション

react-native-svgはグラデーションの色の区切りを<Stop>子要素で定義しますが、これはアニメーションできません。そこでReanimatedはgradientpropを追加しています。これは{ offset, color, opacity }という区切りの配列で、子要素の代わりに使います。各区切りのoffsetcoloropacityをアニメーションでき、区切りの数をfromtoで変えることもできます。グラデーションはネイティブプラットフォームのみで動き(Webでは動きません)、RadialGradientfxfyはiOS専用です。

Web

Webでは、ReanimatedはSVGをCSS経由で動かすため、本物のCSSプロパティに対応する属性だけがアニメーションします。CSSに対応する属性がないもの(PolygonPolylinepointsLineの端点、TextPattern・グラデーションの座標、グラデーションの区切り)はWeb上ではCSSでアニメーションできません。これらにはuseAnimatedPropsを使ってください。

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