No.105
MODAL CSSJSFRAMER

Framer Modal

開くときはスプリングでポップ、閉じるときもふわっと消えるモーダル。Framer MotionのAnimatePresence + spring transitionによる開閉両対応ダイアログ。

DEMO

CODE

HTML
<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 &amp; soft exit.</p>
      <button class="fmd-close" type="button">CLOSE</button>
    </div>
  </div>
</div>
CSS
.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; }
JS
(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 (React)

ライブデモは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

Claude Code Cursor v0
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

📖 なかしまぁ先生のCSSアニメーション入門 📖 UIデザインの心理学 ―わかりやすさ・使いやすさの法則 📖 Fundamentals of Web Animation with GSAP

※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。

学びを止めない · AMAZONサブスク

📚 Kindle Unlimited — 技術書・デザイン書が読み放題。30日無料体験 → 🎧 Audible — 移動中に耳で学ぶオーディオブック。30日無料体験 → 📦 Amazon Prime — Prime Reading・配送特典つき。30日無料体験 → 🎵 Music Unlimited — 作業用BGMに。1億曲が聴き放題・30日無料体験 →

※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。

RELATED EFFECTS

↗ MORE
→ MODAL FADE → MODAL SCALE BLUR → FRAMER ANIMATEPRESENCE

Framer Motion Modal とは

開くときはスプリングで軽くポップし、閉じるときもスケールとフェードでふわっと消えるモーダルダイアログ。Framer Motion の AnimatePresence を使うと、Reactで条件レンダリングされる要素の「消える瞬間」にもアニメーションを付けられるのが最大のポイントです。

overlay(背景の暗幕)と dialog を別々の motion.div にし、overlay は不透明度のみ・dialog はスプリングで動かすと、背景は落ち着いたまま本体だけが気持ちよく弾みます。overlayクリックで閉じる場合は dialog 側で stopPropagation を忘れずに。