DEMO
CODE
<div class="demo-stage fmd-stage">
<button class="fmd-open" type="button">OPEN MODAL</button>
<div class="fmd-overlay" aria-hidden="true">
<div class="fmd-dialog" role="dialog" aria-label="Demo modal">
<p class="fmd-label">FRAMER MODAL</p>
<p class="fmd-text">Spring pop-in & soft exit.</p>
<button class="fmd-close" type="button">CLOSE</button>
</div>
</div>
</div>
.fmd-stage { position:relative; display:flex; align-items:center; justify-content:center; padding:3rem; background:#0F0F12; min-height:300px; overflow:hidden; }
.fmd-open {
padding:1rem 2.4rem; border-radius:999px; cursor:pointer;
font-family:'Oswald',sans-serif; font-size:.85rem; letter-spacing:.2em;
color:#fff; background:#E91B89; border:none; transition:transform .2s ease;
}
.fmd-open:hover { transform:translateY(-2px); }
.fmd-overlay {
position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
background:rgba(0,0,0,.55); opacity:0; visibility:hidden;
transition: opacity .28s ease, visibility 0s linear .28s;
}
.fmd-overlay.fmd-show { opacity:1; visibility:visible; transition: opacity .28s ease; }
.fmd-dialog {
width:min(280px,80%); padding:1.8rem 1.6rem; border-radius:16px; text-align:center;
background:#1A1A1A; border:1px solid rgba(255,255,255,.14);
transform:scale(.86) translateY(14px); opacity:0;
transition: transform .38s cubic-bezier(.34,1.56,.64,1), opacity .25s ease;
}
.fmd-overlay.fmd-show .fmd-dialog { transform:scale(1) translateY(0); opacity:1; }
.fmd-label { font-family:'Oswald',sans-serif; font-size:.7rem; letter-spacing:.3em; color:#E91B89; margin:0 0 .6rem; }
.fmd-text { color:#c9c9d1; font-size:.85rem; margin:0 0 1.2rem; }
.fmd-close {
padding:.55rem 1.6rem; border-radius:999px; cursor:pointer;
font-family:'Oswald',sans-serif; font-size:.72rem; letter-spacing:.2em;
color:#fff; background:transparent; border:1px solid rgba(255,255,255,.3);
}
.fmd-close:hover { border-color:#E91B89; color:#E91B89; }
(function(){
var overlay = document.querySelector('.fmd-overlay');
document.querySelector('.fmd-open').addEventListener('click', function(){
overlay.classList.add('fmd-show');
overlay.setAttribute('aria-hidden', 'false');
});
function close(){
overlay.classList.remove('fmd-show');
overlay.setAttribute('aria-hidden', 'true');
}
document.querySelector('.fmd-close').addEventListener('click', close);
overlay.addEventListener('click', function(e){ if (e.target === overlay) close(); });
})();
ライブデモはFramer Motion不要のCSS等価版(cubic-bezierのオーバーシュートでスプリング近似)。下がReact + Framer Motionの実装です。AnimatePresenceがアンマウント時のexitアニメーションを担保します。
import { useState } from "react"
import { AnimatePresence, motion } from "framer-motion"
export default function Modal() {
const [open, setOpen] = useState(false)
return (
<>
<button onClick={() => setOpen(true)}>OPEN MODAL</button>
<AnimatePresence>
{open && (
<motion.div
className="overlay"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={() => setOpen(false)}
>
<motion.div
className="dialog"
initial={{ scale: 0.86, y: 14, opacity: 0 }}
animate={{ scale: 1, y: 0, opacity: 1 }}
exit={{ scale: 0.92, y: 8, opacity: 0 }}
transition={{ type: "spring", stiffness: 380, damping: 26 }}
onClick={(e) => e.stopPropagation()}
>
<p>FRAMER MODAL</p>
<button onClick={() => setOpen(false)}>CLOSE</button>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</>
)
}
AI PROMPT
開くときはスプリングでポップし、閉じるときもふわっと消えるモーダルをFramer Motionで作って。<AnimatePresence> の中に条件付きで overlay と dialog の <motion.div> を置き、overlay は opacity 0↔1、dialog は initial={{ scale: 0.86, y: 14, opacity: 0 }} → animate={{ scale: 1, y: 0, opacity: 1 }} → exit={{ scale: 0.92, y: 8, opacity: 0 }}。transition={{ type: 'spring', stiffness: 380, damping: 26 }} で弾む開きに。overlayクリックで閉じ、dialog側は stopPropagation。CSSで近似するなら cubic-bezier(.34,1.56,.64,1) のオーバーシュートを使う。確認ダイアログ・ログインモーダルに。
Acceptance criteria: works with prefers-reduced-motion (animation disabled), zero console errors, zero layout shift
AIエージェントでの実装ガイド → Claude Code でWebアニメーションを実装する
BOOKS · PR · AMAZON
※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。
学びを止めない · AMAZONサブスク
※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。
開くときはスプリングで軽くポップし、閉じるときもスケールとフェードでふわっと消えるモーダルダイアログ。Framer Motion の AnimatePresence を使うと、Reactで条件レンダリングされる要素の「消える瞬間」にもアニメーションを付けられるのが最大のポイントです。
overlay(背景の暗幕)と dialog を別々の motion.div にし、overlay は不透明度のみ・dialog はスプリングで動かすと、背景は落ち着いたまま本体だけが気持ちよく弾みます。overlayクリックで閉じる場合は dialog 側で stopPropagation を忘れずに。