経緯
最近Reactを使う機会が増えました。
Reactを夢中で触っているうちに、ふと自分のやってみたかったことの1つを思い出しまして。
別ページを表示するときのあのアニメーションを取り入れてみたい…!
(調べたところページトランジションというみたいですね)
今回は簡単にではありますがそれを実践してみようかと。
使用したライブラリ
- react (v17.0.2)
- react-router-dom (v5.3.0)
- framer-motion (v4.1.17)
今回は環境構築等は省きます。
また、複数のページのルーティングを行っている前提で話を進めます。
アニメーションの実装方法
アニメーション自体は以下のようなコードでも実装できます。
import React from "react";
import {motion} from "framer-motion";
const Home = () => {
return <motion.div initial={{x:"-100%"}} animate={{x:["-100%","0%"]}} >
Home
</motion.div>
}
export default Home;
これは、ページを読み込んだ際にHomeという文字を左画面外から元々の位置へ移動させるアニメーションが実行されるコードです。
ただし、この状態ではこのコンポーネントがマウントされる際のアニメーションはオッケーなのですが、アンマウントされる際のアニメーションが実装できていません。
exit処理が使えるらしい。
調べていくうちに、以下の資料からexitの存在を知りました。
この資料によると…
- animateはマウント時のアニメーションを定義している
- exitはアンマウント時のアニメーションを定義している
- アニメーションしたいコンポーネントの親要素として、AnimatePresenceというコンポーネントが必要
- AnimatePresenceの子コンポーネントにはkeyを定義しておく必要がある
…とのこと。
それっぽく書いてみた
とりあえず書いてみないことには始まらないわけで。それっぽくコードを変更。
import React from "react";
import {motion,AnimatePresence} from "framer-motion";
const Home = () => {
return <AnimatePresence>
<motion.div initial={{x:"-100%"}} animate={{x:["-100%","0%"] exit={{x:["0%","100%"]}}>
Home
</motion.div>
<AnimatePresence>
}
export default Home;
こんな感じ。先ほどのコードに加え、必要なコンポーネントとアンマウント時のアニメーションを定義しました。
…お気づきかもしれませんが、このコード、keyの定義を忘れています。当然エラーが発生し、ここから迷走することに。
keyの定義
これまた色々調べているうちに、子コンポーネントにkeyの定義が必要なことに気づきました。
同時に、keyにはuseLocationなどで取得できるlocationがよく用いられている(?)ことにも気づきます。
まだまだ経験不足ということもあり、keyというものが何かよくわかっていませんでしたが、とりあえず書いてある通り実践してみることに。
locationをkeyに設定した
やり方が分かったので、凡例に倣ってコードを変えてみました。
今回は大元のApp.jsを変更。
import React from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
useLocation,
} from "react-router-dom";
import {AnimatePresence} from "framer-motion";
import Navigation from "./components/Navigation";
import Home from "./components/Home/Home";
import About from "./components/About/About";
import Contact from "./components/Contact/Contact";
import "./App.css";
function App() {
const location = useLocation();
return (
<Router>
<div className="header">Header</div>
<Navigation></Navigation>
<div>
<AnimatePresence exitBeforeEnter>
<Switch location={location} key={location.pathname}>
<Route exact path="/">
<Home></Home>
</Route>
<Route exact path="/about">
<About></About>
</Route>
<Route exact path="/contact">
<Contact></Contact>
</Route>
</Switch>
</AnimatePresence>
</div>
</Router>
);
}
export default App;
ポイントとしては、
- key用のlocation取得にuseLocationを使用
- Switchの親コンポーネントにAnimatePresenceを使用
どうやら各コンポーネントにいちいちAnimatePresenceコンポーネントを記述する必要はなく、Switchの親コンポーネントとしてAnimatePresenceを記述すれば問題ないようです。
この時点で、先ほどHome.tsxで記述したAnimatePresenceコンポーネントは消しています。
…
なんぞこれ…
よく見るCannot read properties of undefinedだけど…
エラー自体はよく見るやつ。ただ…なぜこのタイミング?
エラーが発生してる場所のコードも問題なさげ。誤字もしてない。
原因が分からんんんんんんん~~~~~~~~~~!!!!
原因を探るべく、またしてもネットの海へダイブ。
あれでもない、これでもない…
2時間くらいこれのためだけにネットサーフィンする羽目になりました。勉強が足りん。
そして…
原因判明
このエラー、どうやらRouterとuseLocation(hooks)の位置が悪かったために起きたエラーのようです。
エラーの起きる条件
- Router(BrowserRouter)とhooksが同じコンポーネントファイルにあること
- hooksがRouter(BrowseRouter)の子コンポーネント以下に設置してあること
そんなんしらん…(´・ω・`)
というわけで早速修正。
import React from 'react';
import {
Switch,
Route,
useLocation,
} from "react-router-dom";
import {AnimatePresence} from "framer-motion";
import Navigation from "./components/Navigation";
import Home from "./components/Home/Home";
import About from "./components/About/About";
import Contact from "./components/Contact/Contact";
import "./App.css";
function App() {
const location = useLocation();
return (
<div>
<div className="header">Header</div>
<Navigation></Navigation>
<div>
<AnimatePresence exitBeforeEnter>
<Switch location={location} key={location.pathname}>
<Route exact path="/">
<Home></Home>
</Route>
<Route exact path="/about">
<About></About>
</Route>
<Route exact path="/contact">
<Contact></Contact>
</Route>
</Switch>
</AnimatePresence>
</div>
</div>
);
}
export default App;
App.tsxからRouterを削除し…
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {
BrowserRouter as Router,
} from "react-router-dom";
ReactDOM.render(
<React.StrictMode>
<Router>
<App />
</Router>
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
Appを呼び出しているindex.tsxにRouterを移しました。
無事解決!
動画を見せることはできませんが、無事にマウント時・アンマウント時の両方でアニメーションを実行することができました!これでこれからframer-motionを使って色々遊べそうです。
まとめ
今回はframer-motionを使うときに、useLocationとRouterの使い方で躓いたお話でした。(実はframer-motion自体は間接的に関係しただけで、実際はreact-routerとhooksの問題だったけど。)
keyにlocationを設定しようとしたために躓いたエラーだったけど、今後も出てきそうなエラーだったのでそういう意味では良い経験になったかなと。
あのエラー画面苦手だからあんまり見たくないが…(´-ω-`)
以上です。同じようなエラーに直面した時にはhooks等の位置確認もしてみたら幸せになれるかも?
ではでは。('ω')ノシ
参考文献
今回参考にさせて頂いた記事をもう一度まとめておきます。