React で Modal や Drawer で作る時に state の変更で表示 / 非表示を切り替えるとパッ出たり消えたりするので UX 的に美しくないからアニメーションをさせたい。
chakra UI のような出来上がっている UI ライブラリ無しで実装してみたのでメモ (chakra UI使うと楽なのだが)
Framer Motion を使う
animation を時前で実装すると CSS アニメーションを待つタイマーとかいろいろと状態を作らないと大変そうだし、メンテコストも高そうだったのでライブラリを使うことにした。車輪の再発明をしてる暇はなかった。
Rect で使える animation ライブラリはちょっと検索しただけでもたくさん出てきたが、 Reactアニメーションライブラリって結局何がいいの? この記事を参考に実装コストが低そうだったので Framer Motion を使うことにした。
onClick で 表示 / 非表示 される Modal コンポーネントを作る
import { FC, ReactNode, useCallback } from 'react'; import { createPortal } from 'react-dom'; const App:FC = () => { const { isOpen, onOpen, onClose } = useDisclosure(); return ( <> <button type={onOpen}>Open Modal</button> <Modal isOpen={isOpen} onClose={onClose} /> </> ); }; type ModalProps = { isOpen: boolean; onClose: () => void; } const Modal:FC<ModalProps> = ({ isOpen, onClose }) => { return ( <Portal> {isOpen && <div className="modal"></div>} </Poral> ); } type PoralProps = { children: ReactNode; }; const Poral: FC<PoralProps> = ({ children }) => { return createPortal(children, document.body); }; const useDisclosure = () => { const [isOpen, setIsOpen] = useState<boolean>(false); const onClose = useCallback(() => { setIsOpen(false); }, [setIsOpen]); const onOpen = useCallback(() => { setIsOpen(true); }, [setIsOpen]); return { isOpen, onOpen, onClose }; };
Framer Motion で mount / unmount 時のアニメーションを付ける
Framer Motion を使ってコンポーネントの mount / unmount 時にアニメーションを付けるには <AnimatePresence>
で囲った中に mount / unmount されるコンポーネントを <motion.div>
として作成すれば良い
AnimatePresence
Animate components when they're removed from the React tree.
AnimatePresence
allows components to animate out when they're removed from the React tree.
cf. AnimatePresence | Framer for Developers
import { motion, AnimatePresence } from "framer-motion" export const Modal:FC<ModalProps> = ({ isOpen, onClose, children }) => { return ( <Portal> <AnimatePresence> {isOpen && ( <motion.div className="modal" initial={{ opacity: 0, transform: "translateY(50%)", }} animate={{ opacity: 1, transform: "translateY(0)", }} exit={{ opacity: 0, transform: "translateY(30%)", }} > {children} </motion.div> )} </AnimatePresence> </Poral> ); }
mount 時は initial
-> animate
と CSS アニメーションする・unmound 時は animate
-> exit
と CSS アニメーションする
<motion.div>
は <AnimatePresence>
の直下でなくてもOK
いい感じにアニメーションさせようとすると CSS と CSS アニメーション力が必要になるが、Framer Motion を使えば React で mount / unmount 時にシンプルにアニメーションを付けることができた
Sample
最近は動画の学習コンテンツ多くて助かりすぎる
[参考]