🎉

Framer Motion 試してみる

2022/08/05に公開

Framer Motion という React 用アニメーションライブラリを試してみました。

実施環境
項目 詳細
PC MacBook Pro(14 インチ、2021)Apple M1 Pro
OS MacOS Monterey 12.5
Node.js v16.14.2
Next.js v12.1.0
React.js v17.0.1
Framer Motion v7.0.0

Framer 社

Framer Motion is a production-ready motion library for React from Framer.

Framer Motion は Framer 社が開発した React 用アニメーションライブラリです。
Framer 社は同名のFramerというデザインプラットフォームも展開しています。

Framer has evolved from a simple JavaScript library into a full-blown web design platform.

とのことなので、Framer Motion 等のライブラリ開発から始まり、今は Framer というデザインプラットフォームにまで発展したのかな?という想像です。

Framer
Design, publish, done.
Framer’s canvas is incredible for web design. Create web pages with text, links, media, and animations—no code needed. Ready to ship? Publish your site with a single click.

Framer の方は触ってないので詳しくはわかりませんが、デザインからコードを生成できるようなツールということでしょうか?今度触ってみたいです。

Framer Motion

まず感想から述べておくと、使い方も簡単で、特に React でコンポーネントが消える時のアニメーションが楽になります。
Intersection Observer的機能も簡単に使えるので、React でちょっと手間がかかるアニメーションを気軽に実装できる印象です。
こちらにいくつかサンプルを実装してみたので参考にしていただけますと幸いです。

導入

React(今回は Next)のプロジェクトを用意して、下記でライブラリを追加します。

// yarn
yarn add framer-motion

// npm
npm install framer-motion

あとは motion を import すれば使えます。
もう一つよく使いそうなのは AnimatePresence です。

import { AnimatePresence, motion } from 'framer-motion';
  • motion
    • 実際のアニメーションを設定するコンポーネント
    • <motion.div></motion.div>のように、motion.{タグ名}という形で使います。既存のタグを置き換えられる感じなので、基本的には DOM 構造変えたりする必要はないです
  • AnimatePresence
    • コンポーネント等が消える時にアニメーションするには、こいつで囲ってやる必要があります。(普通にやるとアニメーションせずに先に消えてしまうため)

ページ遷移時のアニメーション

試しに、ページ遷移時にふわっと消えて、ふわっと出てくるようにしたいと思います。参考

実装サンプル
/aから/bへの遷移で確認できます

motion

まず各ページ、または共通化したければ Layout コンポーネントのようなところで motion を設定します。今回は opacity を変化させています。

components/Layout.jsx
import React, { VFC } from 'react';
import { motion } from 'framer-motion';

/** レイアウトコンポーネント */
const Layout = ({ children }) => {
  return (
    <motion.main
      initial={{ opacity: 0 }} // 初期状態
      animate={{ opacity: 1 }} // マウント時
      exit={{ opacity: 0 }} // アンマウント時
    >
      {children}
    </motion.main>
  );
};

export default Layout;

Layout の場合は各ページに Layout を仕込みます。

pages/a.jsx
import styles from './index.module.css';
import Link from 'next/link';
import Layout from '/components/Layout';

const TestPageA = () => {
  return (
    <Layout>
      <div className={styles['main-contents']}>
        <h1>test page A</h1>
        <div className={styles.link}>
          <Link href="/b">
            <a className={styles.button}>To B</a>
          </Link>
        </div>
      </div>
    </Layout>
  );
};

export default TestPageA;

AnimatePresence

このままだと消えるアニメーションができないので、_app.jsで、Component を AnimatePresence で囲います。

_app.js
import { AnimatePresence } from 'framer-motion';

function MyApp({ Component, pageProps, router }) {
  return (
    <AnimatePresence exitBeforeEnter>
      <Component key={router.asPath} {...pageProps} />
    </AnimatePresence>
  );
}

export default MyApp;

exitBeforeEnter

exitBeforeEnter: trueにすると、AnimatePresence はその配下の一つのコンポーネントのみレンダリングします。今回のようにページコンポーネントが切り替わるケースではexitBeforeEnter: trueを設定しないと、前のページの exit アニメーションと、次のページの initial アニメーションが同時に走ってしまうので、exitBeforeEnter: trueを設定することで、前ページの exit アニメーションが完了し、前ページが destroy されてから、次のページのレンダリングが開始されるようになります。

key

あとはコンポーネントが切り替わる今回のようなケースでは、ユニークな key を設定しないとうまくいかないので、 key={router.asPath}を設定しています。

スクロール発火のアニメーション

対象のコンポーネントが view に入ったら発火するアニメーションというのも簡単に実装できます。

対象を motion で囲ってあげて、whileInView というところにアニメーションを設定するだけです。
発火のタイミング等は viewport のオプションで調整できます。

pages/a.jsx
  <motion.div
    className={styles.appear}
    initial={{ opacity: 0 }}
    whileInView={{ opacity: 1 }}
    viewport={{ amount: 'all' }}
    transition={{ duration: 1 }}
  >
    Intersection Observer
  </motion.div>

まとめ

個人的には上記で紹介した消える時のアニメーションと、Intersection Observer 的アニメーションは Framer Motion めちゃめちゃ便利だなと思いました。
その他 Hover 時のアニメーションとかももちろんできますが、よほど複雑でなければそのへんは CSS で十分かなと思ったので、使い分けていきたいなと思います。

参考にさせていただいた記事

Framer

https://www.framer.com/
https://dev.classmethod.jp/articles/tried-to-create-framer-account/

Framer Motion

https://www.framer.com/motion/
https://qiita.com/arrow2nd/items/b16385cf22c567fbbf33
https://zenn.dev/hiyoringa/articles/1e70a63cfdcb17

Discussion