framer-motionでアニメーションを実装してみる
framer-motionとは?
Framer社が開発したReact製アニメーションライブラリ
Framer社はコードベースのデザインツールFramerなども展開している(使ったことはない)
React製UIフレームワーク「Chakra UI」にも使用されている
framer-motionの特徴
宣言的にアニメーションを定義できる
フックスが充実している
アンマウントに対して、アニメーションが適用できる
基本的な使い方
セットアップ
code:zsh
yarn add framer-motion
Motionコンポーネント
code:tsx
import { motion } from 'framer-motion';
<motion.div
initial={{ opacity: 0, y: 30 }} // 初期スタイル
animate={{ opacity: 1, y: 0 }} // アニメーション時のスタイル
transition={{
duration: 1.5,
ease: 'easeInOut',
}}
Motion Component
</motion.div>
motion.{tag}で宣言することでアニメーション化が可能
AnimatePresence
code:tsx
import { AnimatePresence, motion } from 'framer-motion';
<AnimatePresence>
{isOpenDialog && (
<motion.div
key="dialog"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }} // アンマウント時のスタイル
<Dialog onClickOverlay={handleClickOverlay}>Dialog</Dialog>
</motion.div>
)}
</AnimatePresence>
AnimatePresenceコンポーネントでmotionコンポーネントを囲う
exitで定義したアニメーションが完了してから、アンマウントされるようになる
Next.jsで_app.tsx or レイアウト等で使用するとページ遷移時にアニメーションができる
MotionValue
Reactでのstateのような立ち位置
値をセットしても再レンダリングが発生しない
code:tsx
import { useMotionValue } from 'framer-motion';
const opacity = useMotionValue(0);
const handleScroll = () => {
const scrollYProgress = // スクロール値計算
opacity.set(scrollYProgress);
};
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
<motion.div
style={{opacity}}
Scrab Scroll
</motion.div>
応用編
Next.jsでのページ遷移アニメーション
code:_app.tsx
import '../styles/globals.css';
import type { AppProps } from 'next/app';
import { AnimatePresence } from 'framer-motion';
import Layout from '../components/Layout';
const App = ({ Component, pageProps, router }: AppProps) => {
return (
<AnimatePresence mode="wait"> // 遷移元のアニメーションと遷移先のアニメーションが重ならないようにする
<Layout key={router.asPath}>
<Component {...pageProps} />;
</Layout>
</AnimatePresence>
);
};
export default App;
code:Layout.tsx
import { motion } from 'framer-motion';
import { FC, ReactNode } from 'react';
import Header from './Header';
type Props = {
children: ReactNode;
};
const Layout: FC<Props> = ({ children }) => {
return (
<>
<Header />
<motion.main
initial={{ opacity: 0, y: 40 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 40 }}
transition={{
duration: 1,
}}
{children}
</motion.main>
</>
);
};
export default Layout;
mode="wait"
遷移元のexitと遷移先のanimateが重ならないようにする
exitBeforeEnterが非推奨になった
注意
例に挙げた設計だと、ページ遷移するたびにレイアウトごと再レンダリングが発生してしまうので、Layout内で取得したasPathをkeyに設定するなどして、page componentのみ再レンダリングさせるのがおすすめです。
(岡崎)CSS modulesだとbuild時にバグるので注意 isssu スクロールトリガーアニメーション
スクロール要素が画面内に入ってきたタイミングでアニメーションさせる
MotionコンポーネントのwhileInViewに定義したスタイルがアニメーションされる
他にもフォーカス・ホバー・ドラッグ・タップなどのアクションに対して、アニメーションが適用できる
code:tsx
import { motion } from 'framer-motion';
<motion.div
initial={{ maxWidth: 0 }}
whileInView={{ maxWidth: 800 }} // 画面内に入ってきた時のスタイル
viewport={{ amount: 0.5 }}
transition={{ ease: 'easeInOut' }}
Scroll Trigger
</motion.div>
最後に
案件でも導入しましたが、framer-motionは直感的にアニメーションを実装できるのでおすすめです。
Next13での動作も試しましたが、Client Componentだと動作しました。(Server Componentでは動作しなかった)
他におすすめのアニメーションライブラリあれば教えてください。