Nanolier

Framer MotionでNext.jsのページ遷移にアニメーションを付与する

2022/07/07

今回は Next.js で作成したページに Framer Motion を導入してみます。

サンプルコード

こんな感じになりました。

完成品

サンプルコードはこちらになります

パッケージインストール

下記コマンドで Framer Motion をインストールします

yarn add framer-motion

_app.tsx で AnimatePresence を読み込む

公式ドキュメント>AnimatePresenceを参考に、AnimatePresence を読み込みます。

Animate components when they're removed from the React tree.

AnimatePresence は、囲んだコンポーネントがマウント / アンマウントされたときにアニメーションできるようにするためのコンポーネントのようです。
機能的には、

  1. コンポーネントがマウントを解除されるときに子コンポーネントに通知する
  2. アニメーションが完了するまでマウント解除を延期する

とのことです。また、

Direct children must each have a unique key prop so AnimatePresence can track their presence in the tree.

と記載されているように、直下の子要素はそれぞれユニークなキーを属性として持つ必要があるとのこと。
他の方の実装をみると router の asPath などを渡しているようなのでそれに倣います。

実装すると以下のようなコードになります。

※getLayout に関してはNext.js の公式ドキュメントに記載されている通りなので割愛します。

import type { AppPropsWithLayout } from '../types/page';
import { Fragment } from 'react';
import { AnimatePresence } from 'framer-motion';

const MyApp = ({ Component, pageProps, router }: AppPropsWithLayout) => {
  const getLayout = Component.getLayout || ((page) => page);

  return (
    <Fragment>
      {getLayout(
        <AnimatePresence exitBeforeEnter>
          <Component key={router.asPath} {...pageProps} />
        </AnimatePresence>
      )}
    </Fragment>
  );
};

export default MyApp;

各ページで読み込む Motion コンポーネントを作成する

次は各ページでマウント、アンマウント時にアニメーションさせるためのコンポーネントを作成します。

こちらも公式ドキュメント>motionを参考に実装していきます。

今回使用した props は以下となります。

initial

Properties, variant label or array of variant labels to start in. Set to false to initialise with the values in animate (disabling the mount animation).

style プロパティ or variant or variant の配列を渡すことができるようです。false を渡すと animate に渡した値が初期値になるとのこと。

variantes に関しては公式ドキュメント>examplesに説明がありました。

Variants are pre-defined visual states that a component can be in. By giving a component and its children variants with matching names, whole React trees can be animated by changing a single prop. By using variants, a parent can easily orchestrate the animations of its children with special transition props like staggerChildren. Variants can also be dynamic functions that return different props based on data passed to each component's custom prop.

要約するとアニメーションの状態を定義しておくことで簡単に変更ができるようになりますよ、的な感じだと思います。

animate

Values to animate to, variant label(s), or AnimationControls.

style プロパティ or variant(の配列) or AnimationControls が渡せるとのこと。

AnimationControles については提供されている hooks を使うことでより複雑なアニメーションを実装できるようです。
公式ドキュメント>animationに記載されていますが、今回は使用しないため割愛します。

exit

A target to animate to when this component is removed from the tree. This component must be the first animatable child of an AnimatePresence to enable this exit animation. This limitation exists because React doesn't allow components to defer unmounting until after an animation is complete. Once this limitation is fixed, the AnimatePresence component will be unnecessary.

要約するとコンポーネントがアンマウントされるときに呼び出されるアニメーションの指定です。

transition

transition については項目が多いため説明を割愛します。
詳細は公式ドキュメント>transitionを見ながら色々試してみるのがいいかと思います。

上記を使用して以下のようなコンポーネントを作成しました。

import { motion } from 'framer-motion';

type Props = {
  children?: React.ReactNode;
};

const Motion: React.FC<Props> = ({ children }) => {
  return (
    <motion.div
      transition={{
        duration: 0.3,
        ease: 'circOut',
      }}
      initial={'unmount'}
      animate={'mount'}
      exit={'unmount'}
      variants={variants}
    >
      {children}
    </motion.div>
  );
};

const variants = {
  unmount: { opacity: 0, transform: 'translateX(-20%)' },
  mount: {
    opacity: 1,
    transform: 'translateX(0%)',
  },
};

export default Motion;

variants で style をあらかじめ定義しておき、inital, animate, exit にはそれぞれプロパティ名を渡せばアニメーションしてくれます。
また motion.div としていますが、他の要素に変更することも可能なようです。

ページ内で Motion コンポーネントを読みこむ

先ほど実装した内容を各ページで import します

import type { NextPageWithLayout } from '../types/page';
import { Motion, Layout } from '../components';

const Home: NextPageWithLayout = () => {
  return (
    <Motion>
      <p>コンテンツ</p>
    </Motion>
  );
};

Home.getLayout = (page) => <Layout>{page}</Layout>;

export default Home;

これでページ遷移する際にアニメーションさせることができるようになりました。