74
44

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Next.js + Framer Motion でページ遷移アニメーションを実装する

Last updated at Posted at 2022-03-29

まえがき

React のアニメーションライブラリである Framer Motion を利用してページ遷移アニメーションを実装した際に、いくつか引っかかった点があったので、備忘録として残しておこうと思います。

バージョン

  • Next.js v12.1.1
  • Framer Motion v7.6.1

今回実装するもの

anim.gif

↑ こんな感じのふんわりしたフェードイン / フェードアウトを実装します。

手順

1. アンマウント時のアニメーションを有効にする

_app.tsx
import { AnimatePresence } from 'framer-motion'
import type { AppProps } from 'next/app'

import 'styles/globals.css'

const MyApp = ({ Component, pageProps, router }: AppProps) => (
  <AnimatePresence mode="wait">
    <Component key={router.asPath} {...pageProps} />
  </AnimatePresence>
)

export default MyApp

アンマウント時のアニメーションを有効にするため、framer-motion から <AnimatePresence> を import して、<Component> を囲みます。

AnimatePresence

mode="wait" とすることで、コンポーネントのアンマウントを待ち、遷移元のアンマウント時のアニメーションと遷移先のマウント時のアニメーションが同時に発生しないようにしています。

Component

key の値として、初めは router.route を利用していたのですが、この値は pages/ ディレクトリ以下のファイルパス そのもの であり、 Dynamic Routing を使用して生成しているページ([id].tsx みたいなページ)では [id] の部分が展開されず同じ値になってしまうので、「一部ページでアニメーションが再生されない」という問題が起きました。

ですので、ここではブラウザ上で表示されるパスである router.asPathを利用しています。

こんな感じ
// パス = pages/[id]
// id = hoge

// ...

console.log(router.route) // -> "pages/[id]"
console.log(router.asPath) // -> "pages/hoge"

2. 遷移アニメーションを設定

hoge.tsx
import { motion } from 'framer-motion'
import type { NextPage } from 'next'

const Hoge = (): NextPage => (
  <motion.div
    initial={{ opacity: 0 }} // 初期状態
    animate={{ opacity: 1 }} // マウント時
    exit={{ opacity: 0 }}    // アンマウント時
  >
   // ...
  </motion.div>
)

export default Hoge

全体を <motion> コンポーネントで囲み、任意の遷移アニメーションを設定します。

詳しい Props については、Motion components をご覧ください...。

3. ページ遷移時の挙動を修正

bug.gif

ここまででアニメーション自体は実装できているのですが、スクロールしてからページ遷移を行うと、「アニメーション再生前にページが先頭に戻る」という挙動になってしまいます。

これでは少し不恰好なので、修正していきます。

1. リンクを修正

fuga.tsx
import Link from 'next/link'

const Fuga = (): JSX.Element => (
  <div>
-   <Link href="https://example.com/">
+   <Link href="https://example.com/" scroll={false}>
      リンク!
    </Link>
  </div>
)

export default Fuga

ページの遷移が発生する箇所の <Link> コンポーネントに scroll={false} を追加して、遷移後の自動スクロールを無効化します。

2. _app.tsxを修正

_app.tsx
import { AnimatePresence } from 'framer-motion'
import type { AppProps } from 'next/app'

import 'styles/globals.css'

const MyApp = ({ Component, pageProps, router }: AppProps) => (
- <AnimatePresence mode="wait">
+ <AnimatePresence mode="wait" onExitComplete={() => window.scrollTo(0, 0)}>
    <Component key={router.asPath} {...pageProps} />
  </AnimatePresence>
)

export default MyApp

onExitComplete={() => window.scrollTo(0, 0)} を追加して、アンマウント完了後にページを先頭まで戻すようにします。

good.gif

これでOKです! 🎉

あとがき

Framer Motion について、かなりシンプルな記述でアニメーションを実装することができ、とても楽しいなという印象を持ちました。

2Dアニメーションだけでなく 3Dアニメーション も扱えたりするようなので、もう少し色々触ってみようと思います!

参考

74
44
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
74
44

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?