🕺

Framer Motionでいい感じのアニメーションをシンプルに実装しました [React / Next.js / App Router]

2024/01/18に公開

こんにちは。nakaatsuと申します。今回はポートフォリオサイトを作り直して個人ブログを作りました。
https://www.nakaatsu.com/
https://www.nakaatsu.com/blog/lip7se2gqm_y
この記事では今回使用したアニメーションライブラリのFramer Motionについて紹介し、実際に実装したアニメーションについて解説します。

Framer Motionとは

https://www.framer.com/motion/
Framerが提供するオープンソースのReact用モーションライブラリです。
公式のドキュメントやexampleが多くあるので導入したいアニメーションの引き出しを考えながら実装しやすいです。

構成

今回は以下の構成でFramer Motionを実装します。

  • Next.js / App Router
  • Framer Motion

セットアップ

React環境のプロジェクトにframer-motionパッケージを追加します。

yarn add framer-motion

アニメーション実装

まずは以下の要素にFramer Motionを追加してアニメーションを設定してみます。

FramerMotionTest/page.tsx
import styles from './FramerMotionTest.module.css';

const FramerMotionTest = () => {
  return (
    <div className={styles.framerMotionContainer}>
      <div className={styles.box}>
        <p>うおお</p>
      </div>
    </div>
  )
}

export default FramerMotionTest;

1. ふわっと表示

Framer Motionはimport { motion } from 'framer-motion'でインポートし、<motion.要素名>の形で取り扱うことができます。
まずは白いBox要素をふわっと表示させてみます。

'use client'

import { motion } from 'framer-motion';
import styles from './FramerMotionTest.module.css';

const FramerMotionTest = () => {
  return (
    <div className={styles.framerMotionContainer}>
      <motion.div className={styles.box} initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: 0.4, delay: 0.3 }}>
        <p>うおお</p>
      </motion.div>
    </div>
  )
}

export default FramerMotionTest;
  1. initial: 要素が存在しない状態から現れるようにするため、不透明度を0に設定。
  2. animate: 不透明度を1にしてアニメーション後に要素が表示されるようにします。
  3. transition: durationでアニメーションにかかる時間を0.4秒に、delayで0.3秒後にアニメーションが開始されるようにします。

これで<motion.div>で囲ったBoxがふわっと表示されるようになりました。要素名にmotion.を加えて値を設定するだけで既存コンポーネントにアニメーションを実装できるので扱いやすいです。

2. ぽよんと表示

ふわっと表示するアニメーションはCSSのみでも比較的実装しやすいものですが、Framer Motionではシンプルな記述でちょっといい感じのアニメーションを実装することができます。

次に右下からぽよんと飛び出してくるようなアニメーションを実装します。

const FramerMotionTest = () => {
  return (
    <div className={styles.framerMotionContainer}>
      <motion.div className={styles.box} initial={{ x: 400, y: 200, scale: 0 }} animate={{ x: 0, y: 0, scale: 1 }} transition={{ duration: 0.6, delay: 0.6, type: 'spring', bounce: 0.3 }}>
        <p>うおお</p>
      </motion.div>
    </div>
  )
}
  1. initial: 表示位置を初期位置の0, 0から右下に配置し、大きさをscaleで0に指定します。
  2. animate: 表示位置を初期位置に戻し、大きさを1に指定し戻します。
  3. transition: type: 'spring' を指定するとぽよんと跳ねるアニメーションにできます。bounceで剛性を設定でき、跳ねたときのぽよぽよ具合を調整できます。

transitiontypeを指定するだけで、ライブラリが用意しているいい感じのアニメーションにすることができます。

このぽよんとしたアニメーションを、設定値を変えるとどう変化するのか試してみます。initialのyをマイナス値にしてbounceを0.8に上げてみます。

<motion.div className={styles.box} 
initial={{ x: 0, y: -200, scale: 0 }}
animate={{ x: 0, y: 0, scale: 1 }}
transition={{ duration: 0.6, delay: 0.6, type: 'spring', bounce: 0.8 }}>

上から勢いよく跳ねて登場するようになりました。bounceの値を上げることで前よりも強く引いて離したようなアニメーションになっているのが分かるかと思います。

3. 表示領域に入ったら動かす

ここまでanimateでアニメーション後の値を指定していましたが、これだと要素が読み込まれたときから動き出してしまいます。
しかし、ファーストビューにない要素をアニメーションさせたいときは要素がユーザー側で実際に表示されたときに動かしたいです。その場合はanimatewhileInViewにすると表示されたら動くように指定できます。

const FramerMotionTest = () => {
  return (
    <>
      <div className={styles.framerMotionContainer}>
        <div className={styles.box}>
          <p>うおお</p>
        </div>
      </div>
      <div className={styles.framerMotionContainerTwo}>
        <motion.div className={styles.box} initial={{ x: 48, y: 48, scale: 0 }} whileInView={{ x: 0, y: 0, scale: 1 }} transition={{ duration: 0.6, delay: 0.6, type: 'spring', bounce: 0.8 }}>
          <p>うおお</p>
        </motion.div>
      </div>
    </>
  )
}

これで要素が視界に入ったら動くようになりました。このままだと要素が表示域から外れる->表示域に入るを繰り返すたびにアニメーションするので、一度だけ動かしたい場合はviewport={{ once: true }}を追加します。
表示域とアニメーション開始位置をずらしたい場合は、viewport={{ margin: '120px' }}のように指定することで実現できます。

4. スクロール量に合わせたアニメーション

whileInViewはスクロールされて要素が表示されたか否かで実行するアニメーションでしたが、どれくらいスクロールされたかによって要素を変形させることもできます。
Framer MotionではuseScroll()を使うことでスクロールの割合を取得することができます。縦スクロールの割合をscrollYProgressで取得してプログレスバーとして表示させてみます。

ProgressBar/index.tsx
'use client'

import { motion, useScroll } from "framer-motion"
import styles from './ProgressBar.module.css'

const ProgressBar = () => {
  const { scrollYProgress } = useScroll()

  return (
    <motion.div className={styles.progressBar} style={{ scaleX: scrollYProgress }} />
  )
}

export default ProgressBar
ProgressBar.module.css
.progressBar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 80px;
  background: 'chartreuse';
  transform-origin: 0%;
  z-index: 999;
}

scrollYProgress: 縦スクロールの進み具合を取得できます。scaleXの値に割り当てることで横方向への変形を縦スクロールに連動させています。

スクロール量の取得をとてもシンプルに行うことができます。今回は単純に横サイズ変形に利用しましたが、Parallaxのような視差表示にも応用することができます。
公式サンプル:https://codesandbox.io/p/sandbox/framer-motion-parallax-i9gwuc?file=%2Fsrc%2FApp.tsx

5. ホバー・タップ

ボタン要素などユーザーにアクションさせる要素はマウスホバー時や画面タップ時にアニメーションをさせたいです。そんなときはwhileHover whileTapを設定するとそれぞれアニメーションさせることができます。

const FramerMotionTest = () => {
  return (
    <div className={styles.framerMotionContainer}>
      <motion.button className={styles.box} whileHover={{ scale: 1.2, rotate: 360, backgroundColor: 'aquamarine', transition: { duration: 0.3 } }} whileTap={{ scale: 0.8, backgroundColor: 'beige' }}>
        <p>うおお</p>
      </motion.button>
    </div>
  )
}
  1. whileHover: ちょっと大きくなって1回転させました。
  2. whileTap: ちょっと小さくしてボタンを押したときに凹むイメージにしました。

PCとスマホでマウスホバーと画面タップのアニメーションを考える際に、短い記述でどちらも実装することができます。

おわりに

  • ドキュメントやサンプルが充実していて学習コストが低め
  • motion.を追加し設定値を入れるだけでアニメーションさせることができる
    • 既存コードを大きく変更せずに導入できる
  • シンプルなアニメーションからスクロールを利用したものまで、今のウェブサイトで実装できるアニメーションが網羅されており様々なライブラリを入れる必要がない
  • 動作が比較的軽い

など自分としてはなかなかおすすめできるアニメーションライブラリでした。Reactプロジェクトにアニメーションを導入したい・アニメーションの習熟をしたい方にはおすすめです。
今回は初めて導入したこともありシンプルなアニメーションを多く実装していますが、他にも要素をドラッグしたときのアニメーションやKeyframeでの制御など紹介しきれていない機能が数多くあります。
ぜひ他の機能も試して楽しいウェブサイトを作ってみてください。

Discussion