[React + Emotion]アニメーションが完了してからアンマウントさせる
はじめに
この記事ではライブラリを使用せず、アニメーションが完了した後にアンマウントする実装について紹介します。
アニメーションが完了した後にアンマウントすることで、以下のGIFのようにスライドアウトアニメーションが最後まで実行できる様になります。
この挙動を実現するため、何が問題となるのかを先に紹介します。
問題点
Reactでコンポーネントが表示される時、コンポーネントが非表示になる時にアニメーションをさせたい場合、コンポーネントの表示・非表示の状態をブール値で持つ以下のような実装が思いつきます。
以下の実装では、ボタンを押下することでスライドインアニメーションで吹き出しが現れ、吹き出しが表示されている状態で再度ボタンを押下するとスライドアウトアニメーションで吹き出しが非表示になる事を期待しています。
(ReactとEmotionを利用して記述しています。本題と関係のない記述は省いています。)
export const Hoge = () => {
const [isBalloonVisible, setBalloonVisibility] = useState<boolean>(false)
return (
<Container>
<Button onClick={() => setBalloonVisibility((prev) => !prev)}>
ボタン
</Button>
{isBalloonVisible && (
<BalloonContainer isBalloonVisible={isBalloonVisible}>
<Balloon>テキストテキストテキスト</Balloon>
</BalloonContainer>
)}
</Container>
)
}
const BalloonContainer = styled.div<{ isBalloonVisible: boolean }>`
animation: ${({ isBalloonVisible }) =>
isBalloonVisible ? slideIn : slideOut}
300ms forwards;
`
こちらのコンポーネントのアニメーションは以下のGIFの様になります。
「はじめに」のGIFではスライドアウトアニメーションで吹き出しが消えましたが、以下のGIFでは吹き出しが一瞬で消えてしまっています。
これはスライドアウトアニメーションが完了する前に吹き出しコンポーネントがアンマウントされているためです。
次にコンポーネントが非表示になる時のアニメーションの実行が完了してからコンポーネントをアンマウントする方法を記述します。
解決策
react-transition-groupというライブラリを利用すると上記問題を解決できるようですが[1]、2024年9月現在、こちらのライブラリの最終リリースが2022年8月1日となっていること[2]、あまりプロジェクトにライブラリを追加したくないなどの理由でライブラリを使わずに実装したので、その方法を記述します。
実装の要点は以下です。
- コンポーネントの状態を
visible
・hiding
・hidden
の3つで持つ - コンポーネントが
hidden
状態以外の場合、コンポーネントをマウントさせる - コンポーネントが
visible
状態の際、再度ボタンを押下することで、コンポーネントをhiding
状態にする- コンポーネントが
hiding
状態の際、コンポーネントが非表示になる時のアニメーションを実行させる
- コンポーネントが
-
onAnimationEnd
でコンポーネントの状態をhidden
にする(アンマウントさせる)
const BALLOON_STATUS = {
visible: 'visible',
hiding: 'hiding',
hidden: 'hidden',
} as const
type BalloonStatus = (typeof BALLOON_STATUS)[keyof typeof BALLOON_STATUS]
export const Hoge = () => {
const [balloonStatus, setBalloonStatus] = useState<BalloonStatus>(
BALLOON_STATUS.hidden
)
const changeBalloonStatus = () => {
if (balloonStatus === BALLOON_STATUS.hidden) {
setBalloonStatus(BALLOON_STATUS.visible)
return
}
if (balloonStatus === BALLOON_STATUS.visible) {
setBalloonStatus(BALLOON_STATUS.hiding)
}
}
const hideBalloon = () => {
if (balloonStatus === BALLOON_STATUS.hiding) {
setBalloonStatus(BALLOON_STATUS.hidden)
}
}
return (
<Container>
<Button onClick={changeBalloonStatus}>ボタン</Button>
{balloonStatus !== BALLOON_STATUS.hidden && (
<BalloonContainer
balloonStatus={balloonStatus}
onAnimationEnd={hideBalloon}
>
<Balloon>テキストテキストテキスト</Balloon>
</BalloonContainer>
)}
</Container>
)
}
const BalloonContainer = styled.div<{ balloonStatus: BalloonStatus }>`
${({ balloonStatus }) => {
switch (balloonStatus) {
case BALLOON_STATUS.visible:
return css``
case BALLOON_STATUS.hiding:
return css``
}
}};
`
こちらのコンポーネントのアニメーションは以下のGIFの様になります。
コンポーネントが非表示になる時のアニメーションの実行が完了した後、コンポーネントがアンマウントされるようになります。
まとめ
- Reactでアニメーションの完了を待ってからアンマウントさせるにはひと手間必要
- アニメーションさせるコンポーネントの状態を
visible
・hiding
・hidden
で持つ -
onAnimationEnd
でコンポーネントをアンマウントさせる
- アニメーションさせるコンポーネントの状態を
- 特に理由がなければreact-transition-groupを導入しても良さそう
Discussion