背景
- framer-motionを使うことで、React Componentのマウント・アンマウント時にアニメーションを定義することができる。
- このうち、アンマウントについては、 対象のコンポーネントを
AnimatePresence
コンポーネントでラップする必要があるが、少しクセがあるので、ここで説明する。
基本的な利用方法
- ロードが完了したら(
loaded=true
)、Spinner
をフェードアウトして、Content
コンポーネントをフェードインさせる例。 - 必ず、AnimatePresence の直下のコンポーネントに
key
を付ける必要がある。 -
Spinner
のフェードアウトを待ってからContent
をフェードインさせたいのでexitBeforeEnter
を付ける。
<AnimatePresence exitBeforeEnter>
{loaded && (
<motion.div key="content" init={{opacity: 0}} animate={{opacity: 1}} exit={{opacity: 0}}>
<Content />
</motion.div>
)}
{!loaded && (
<motion.div key="spinner" init={{opacity: 0}} animate={{opacity: 1}} exit={{opacity: 0}}>
<Spinner />
</motion.div>
)}
</AnimatePresence>
その他できること
- 上記は単純なフェードイン・フェードアウトだが、直下のコンポーネントに
key
をつけることを守っていれば、アニメーションは、子孫のコンポーネントで定義することもできる。 - 直下のコンポーネントは、自作のコンポーネントでもOK。
<AnimatePresence exitBeforeEnter>
{loaded && (
<Content key="content" />
)}
{!loaded && (
<Spinner key="spinner" />
)}
</AnimatePresence>
// Spinner.tsx
<div>
<motion.div init={{opacity: 0}} animate={{opacity: 1}} exit={{opacity: 0, x: -1}}>
loadが終わったら、左に消える
</motion.div>
<motion.div init={{opacity: 0}} animate={{opacity: 1}} exit={{opacity: 0, x: 1}}>
loadが終わったら、右に消える
</motion.div>
</div>
できないこと
- AnimatePresenceをネストさせると、子のAnimatePresence以下にあるexitアニメーションは効かなくなってしまう。
- 下記の例の場合、
<div key="content">
のアンマウント時に、<motion.div key="content1">
の アニメーション (exit={{opacity: 0, x: -1}}
)は効かなくなる。 - https://github.com/framer/motion/issues/746
<AnimatePresence exitBeforeEnter>
{loaded && (
<div key="content">{/* ここのアンマウント時に */}
<AnimatePresence exitBeforeEnter>
{tab1 && (
<motion.div key="content1" init={{opacity: 0}} animate={{opacity: 1}} exit={{opacity: 0, x: -1}}>
<Content1 />{/* ここのアニメーションは効かない */}
</motion.div>
)}
{tab2 && (
<motion.div key="content2" init={{opacity: 0}} animate={{opacity: 1}} exit={{opacity: 0, x: -1}}>
<Content2 />
</motion.div>
)}
</AnimatePresence>
</div>
)}
{!loaded && (
<div key="spinner">
<Spinner />
</div>
)}
</AnimatePresence>