モーダルの開き方 5種類(Fade/Slide/Zoom/Flip/Rotate 以外)
本記事では「Drawer(右から)/ Drop(上から)/ Bounce(弾む)/ Blur(ぼかし解除)/ Skew(斜めから)」の5パターンを紹介。各パターンは「デモ」+「HTML/CSS/JS の単体動作版コード(コピー可)」で構成されています。
コピー時のポイント
CSS/JS はそのまま外部ファイル化も可能です。HTML の id(例:
cp-modal-fade
)と JS 内の参照が一致していることをご確認ください。
コードについて
本記事のコードは AI(ChatGPT)による生成をベースに作成・調整しています。ご利用の環境でテストの上ご使用ください。
免責
本コードの利用に伴う不具合・損害について、当サイトは責任を負いません。自己責任にてご利用ください。
① ドロワー(Drawer:右から)
Drawer モーダル
右側からスッと現れるドロワー風。背景クリック/ESC/「閉じる」で閉じられます。
<!-- Drawer:HTML(単体動作版) -->
<div class="cp-modal" id="cp-modal-drawer" data-anim="drawer">
<button class="btn" type="button" data-open>このアニメで開く</button>
<div class="layer" aria-hidden="true"> <!-- 背景クリックで閉じる対象(JSで判定) -->
<div class="dialog anim" role="dialog" aria-modal="true" aria-labelledby="title-drawer" tabindex="-1">
<h3 id="title-drawer">Drawer モーダル</h3>
<p>右側からスライドインします。ESC/背景クリック/ボタンで閉じられます。</p>
<button class="btn" type="button" data-close>閉じる</button>
</div>
</div>
</div>
/* ===============================================
ボタン(最小デザイン)
=============================================== */
.btn{ /* モーダルの開閉トリガーや閉じるボタンの共通スタイル */
appearance:none; /* ブラウザ既定のボタンスタイルを打ち消す */
border:0; /* 枠線を消す */
border-radius:12px; /* 角丸でタップしやすく */
padding:10px 14px; /* クリック/タップ領域を確保 */
background:#0b6bff; /* アクセントカラー */
color:#fff; /* 文字色は白 */
cursor:pointer; /* ホバー時にポインタ表示 */
} /* /.btn */
.btn:active{ /* 押下時のフィードバック */
transform:translateY(1px); /* 1pxだけ沈ませて押した感を出す */
} /* /.btn:active */
/* ===============================================
暗幕レイヤー(背景の黒半透明カバー)
=============================================== */
.cp-modal .layer{ /* モーダル直下のオーバーレイ要素 */
position:fixed; /* ビューポートに固定配置(スクロールに追従) */
inset:0; /* 上下左右0で全画面を覆う */
background:rgba(2,8,23,.5);/* 暗めの半透明背景(#020817 相当の 50%) */
display:grid; /* グリッドを使って中央寄せを簡易に行う */
place-items:center; /* 子要素(ダイアログ)を中央配置 */
opacity:0; /* 初期は透明で非表示 */
pointer-events:none; /* 初期はクリック等を無効化 */
transition:.2s; /* フェードの滑らかさ(opacity 切替) */
z-index:9999; /* ほぼ最前面に表示 */
} /* /.cp-modal .layer */
.cp-modal[data-open="1"] .layer{ /* data-open="1" が付与された時の見え方 */
opacity:1; /* オーバーレイを表示(フェードイン) */
pointer-events:auto; /* 背景クリックを有効化(閉じる判定に使用) */
} /* /.cp-modal[data-open="1"] .layer */
/* ===============================================
ダイアログ(モーダル本体カード)
=============================================== */
.cp-modal .dialog{ /* 実際に中身を表示するカード */
background:#fff; /* 白背景でコントラストを確保 */
color:#0f172a; /* 濃いインク色の文字で可読性を確保 */
border-radius:12px; /* 柔らかい角丸カード */
padding:20px; /* 内側の余白 */
width:min(680px,92vw); /* 最大680px。スマホでは画面幅の92%に収める */
max-height:82vh; /* 高さは画面の約8割まで(はみ出し防止) */
overflow:auto; /* 内容が溢れたらスクロール可能 */
outline:0; /* :focus時の既定アウトラインは無効(外側でリング管理) */
box-shadow:0 10px 30px rgba(0,0,0,.2); /* 影で浮かせ、背景と分離 */
} /* /.cp-modal .dialog */
/* ===============================================
アニメ対象の初期状態(Drawer 用)
=============================================== */
.anim{ /* .dialog に併用してアニメの初期値を与える */
opacity:0; /* まずは透明 */
transform:translateX(24px);/* 右に24pxずらしてオフセット(後で0に戻す) */
} /* /.anim */
/* 表示中に適用するアニメの共通パラメータ */
.cp-modal[data-open="1"] .anim{ /* data-open="1" の間だけ再生・保持 */
animation-duration:.6s; /* 0.6秒で再生 */
animation-fill-mode:forwards; /* 再生完了後に最終フレームを保持(opacity:1 等) */
animation-timing-function:cubic-bezier(.2,.7,.2,1); /* 少し弾む滑らかさ */
} /* /.cp-modal[data-open="1"] .anim */
/* ===============================================
キーフレーム(右 → 0 へ、透明 → 不透明へ)
=============================================== */
@keyframes cpDrawerIn{ /* Drawer 専用の登場アニメ */
from{ /* 再生開始フレーム */
opacity:0; /* 透明から */
transform:translateX(24px); /* 右へ24pxオフセットから */
} /* /from */
to{ /* 再生終了フレーム */
opacity:1; /* 不透明に */
transform:translateX(0); /* オフセットを 0 に戻して定位置へ */
} /* /to */
} /* /@keyframes cpDrawerIn */
/* data-anim="drawer" のモーダルで、表示中のみ上記アニメを適用 */
.cp-modal[data-anim="drawer"][data-open="1"] .anim{
animation-name:cpDrawerIn; /* 使用するキーフレーム名を指定 */
} /* /.cp-modal[data-anim="drawer"][data-open="1"] .anim */
/* ===============================================
モーション軽減(OS/ブラウザ設定を尊重)
=============================================== */
@media (prefers-reduced-motion:reduce){ /* ユーザーが動きを減らす設定のとき */
.cp-modal[data-open="1"] .anim{ /* アニメ対象 */
animation:none; /* アニメーションを無効化 */
opacity:1; /* すぐに表示 */
transform:none; /* 位置を通常状態に */
} /* /.cp-modal[data-open="1"] .anim */
.cp-modal .layer{ /* 暗幕のフェード */
transition:none; /* トランジションも無効化し即時切替 */
} /* /.cp-modal .layer */
} /* /@media (prefers-reduced-motion:reduce) */
// Drawer:JS(単体動作版)
(function(){
var modal=document.getElementById('cp-modal-drawer'); if(!modal) return;
var layer=modal.querySelector('.layer'), dialog=modal.querySelector('.dialog');
var openBtn=modal.querySelector('[data-open]'), closeBtn=modal.querySelector('[data-close]');
var lastTrigger=null; // 閉じたあとフォーカスを戻す先
function onKey(e){ if(e.key==='Escape') close(); } // ESCで閉じる
function open(trigger){
lastTrigger=trigger||null; // フォーカス返却用に保持
modal.setAttribute('data-open','1'); // 表示フラグ(CSSが反応)
dialog && dialog.focus(); // キーボード操作の起点に
document.addEventListener('keydown',onKey); // ESC待機
}
function close(){
modal.removeAttribute('data-open'); // 非表示
document.removeEventListener('keydown',onKey);
if(lastTrigger && lastTrigger.focus) lastTrigger.focus(); // 元のボタンへ
}
openBtn && openBtn.addEventListener('click',function(){ open(openBtn); });
closeBtn && closeBtn.addEventListener('click',close);
layer && layer.addEventListener('click',function(e){ if(e.target===layer) close(); }); // 背景のみで閉じる
})();
② ドロップ(Drop:上から)
Drop モーダル
上から落ちて軽く反発するように表示。背景クリック/ESC/「閉じる」で閉じられます。
<!-- Drop:HTML(単体動作版) -->
<div class="cp-modal" id="cp-modal-drop" data-anim="drop">
<button class="btn" type="button" data-open>このアニメで開く</button>
<div class="layer" aria-hidden="true">
<div class="dialog anim" role="dialog" aria-modal="true" aria-labelledby="title-drop" tabindex="-1">
<h3 id="title-drop">Drop モーダル</h3>
<p>上から落ちて軽く反発するように表示。背景クリック/ESC/「閉じる」で閉じられます。</p>
<button class="btn" type="button" data-close>閉じる</button>
</div>
</div>
</div>
/* ===============================================
ボタン(最小デザイン)
=============================================== */
.btn{ /* モーダル開閉トリガー用の共通ボタン */
appearance:none; /* 既定のネイティブ装飾を無効化 */
border:0; /* 枠線なしでフラットに */
border-radius:12px; /* 角丸でタップしやすく */
padding:10px 14px; /* クリック/タップ領域を確保 */
background:#0b6bff; /* アクセントカラー(ブルー) */
color:#fff; /* 文字色は白でコントラスト確保 */
cursor:pointer; /* ポインタカーソルで可クリックを示す */
} /* /.btn */
.btn:active{ /* 押下(Active)時の触感表現 */
transform:translateY(1px); /* 1pxだけ沈ませて押した感じを出す */
} /* /.btn:active */
/* ===============================================
暗幕レイヤー(背景の半透明カバー)
=============================================== */
.cp-modal .layer{ /* モーダル直下の全画面オーバーレイ */
position:fixed; /* ビューポートに固定配置(スクロールに追従) */
inset:0; /* 上下左右0で全画面を覆う */
background:rgba(2,8,23,.5); /* 濃い紺色系の50%半透明で背景を落とす */
display:grid; /* グリッドで中央寄せを簡易に実現 */
place-items:center; /* 子要素(.dialog)を中央に配置 */
opacity:0; /* 初期は透明で見えない */
pointer-events:none; /* 初期はクリックなどのイベントを遮断 */
transition:.2s; /* opacity の変化を0.2秒でフェード */
z-index:9999; /* ほぼ最前面へ(ヘッダー等より前) */
} /* /.cp-modal .layer */
.cp-modal[data-open="1"] .layer{ /* data-open="1" が付与されたときの状態 */
opacity:1; /* 暗幕をフェードインして可視化 */
pointer-events:auto; /* 背景クリックを有効化(閉じる判定に使用) */
} /* /.cp-modal[data-open="1"] .layer */
/* ===============================================
ダイアログ(モーダル本体カード)
=============================================== */
.cp-modal .dialog{ /* コンテンツを表示する本体カード */
background:#fff; /* 白背景で読みやすく */
color:#0f172a; /* 文字色は濃いインク色でコントラスト良好 */
border-radius:12px; /* 角丸でやわらかい印象 */
padding:20px; /* 内側余白で要素の詰まりを回避 */
width:min(680px,92vw); /* 最大幅680px、モバイルでは画面幅の92% */
max-height:82vh; /* 高さは画面の約8割まで(はみ出し防止) */
overflow:auto; /* 内容が溢れたら内部スクロール */
outline:0; /* 既定のアウトラインは無効(外側で管理) */
box-shadow:0 10px 30px rgba(0,0,0,.2); /* 影で背景から浮かせる */
} /* /.cp-modal .dialog */
/* ===============================================
アニメ対象の初期状態(Drop 用)
=============================================== */
.anim{ /* .dialog と併用してアニメの初期値を付与 */
opacity:0; /* まずは透明 */
transform:translateY(-20px); /* 上に20pxずらして上方から出てくる起点にする */
} /* /.anim */ /* 初期:やや上から */
/* 表示中のアニメ共通パラメータ(時間・保持・イージング) */
.cp-modal[data-open="1"] .anim{ /* data-open="1" の間だけ再生/保持 */
animation-duration:.6s; /* 0.6秒で再生(ややゆったり) */
animation-fill-mode:forwards; /* 再生完了後の最終フレームを保持 */
animation-timing-function:cubic-bezier(.2,.7,.2,1); /* 立ち上がり良く、収束も自然に */
} /* /.cp-modal[data-open="1"] .anim */
/* ===============================================
キーフレーム(上から落ちて少し通り過ぎ→戻る)
=============================================== */
@keyframes cpDropIn{ /* Drop 専用の登場アニメ */
0%{ /* 再生開始:高い位置&透明 */
opacity:0; /* 透明 */
transform:translateY(-20px); /* 上に20pxオフセット */
} /* /0% */
70%{ /* 7割時点:少し行き過ぎて下へ */
opacity:1; /* ここで不透明化を完了 */
transform:translateY(4px); /* 4px下までオーバーシュート */
} /* /70% */
100%{ /* 最終フレーム:定位置に収束 */
opacity:1; /* 不透明のまま */
transform:translateY(0); /* 0px(定位置)に戻る */
} /* /100% */
} /* /@keyframes cpDropIn */
/* data-anim="drop" のモーダルで、表示中のみ上記アニメを適用 */
.cp-modal[data-anim="drop"][data-open="1"] .anim{
animation-name:cpDropIn; /* 使用するキーフレームを指定 */
} /* /.cp-modal[data-anim="drop"][data-open="1"] .anim */
/* ===============================================
モーション軽減(ユーザー設定を尊重)
=============================================== */
@media (prefers-reduced-motion:reduce){ /* 動きを減らす設定が有効な場合 */
.cp-modal[data-open="1"] .anim{ /* アニメ対象要素 */
animation:none; /* アニメーションを無効化 */
opacity:1; /* 即座に表示 */
transform:none; /* 変形を取り消し(定位置に表示) */
} /* /.cp-modal[data-open="1"] .anim */
.cp-modal .layer{ /* 暗幕のフェードも */
transition:none; /* トランジションを無効化して即時切替 */
} /* /.cp-modal .layer */
} /* /@media (prefers-reduced-motion:reduce) */
// Drop:JS(単体動作版)
(function(){
var modal=document.getElementById('cp-modal-drop'); if(!modal) return;
var layer=modal.querySelector('.layer'), dialog=modal.querySelector('.dialog');
var openBtn=modal.querySelector('[data-open]'), closeBtn=modal.querySelector('[data-close]');
var lastTrigger=null;
function onKey(e){ if(e.key==='Escape') close(); }
function open(trigger){
lastTrigger=trigger||null;
modal.setAttribute('data-open','1');
dialog && dialog.focus();
document.addEventListener('keydown',onKey);
}
function close(){
modal.removeAttribute('data-open');
document.removeEventListener('keydown',onKey);
if(lastTrigger && lastTrigger.focus) lastTrigger.focus();
}
openBtn && openBtn.addEventListener('click',function(){ open(openBtn); });
closeBtn && closeBtn.addEventListener('click',close);
layer &&layer.addEventListener('click',function(e){ if(e.target===layer) close(); });
})();
③ バウンス(Bounce:弾む)
Bounce モーダル
少し大きくなってから落ち着くバウンス演出。背景クリック/ESC/「閉じる」で閉じられます。
<!-- Bounce:HTML(単体動作版) -->
<div class="cp-modal" id="cp-modal-bounce" data-anim="bounce">
<button class="btn" type="button" data-open>このアニメで開く</button>
<div class="layer" aria-hidden="true">
<div class="dialog anim" role="dialog" aria-modal="true" aria-labelledby="title-bounce" tabindex="-1">
<h3 id="title-bounce">Bounce モーダル</h3>
<p>拡大→少しオーバー→収束で弾む印象に。ESC/背景クリック/ボタンで閉じます。</p>
<button class="btn" type="button" data-close>閉じる</button>
</div>
</div>
</div>
/* ===============================================
ボタン(最小デザイン)
=============================================== */
.btn{ /* モーダル開閉トリガー用の共通ボタン */
appearance:none; /* 既定のネイティブ装飾を無効化 */
border:0; /* 枠線なしでフラットに */
border-radius:12px; /* 角丸でタップしやすく */
padding:10px 14px; /* クリック/タップ領域を確保 */
background:#0b6bff; /* アクセントカラー(ブルー) */
color:#fff; /* 文字色は白でコントラスト確保 */
cursor:pointer; /* ポインタカーソルで可クリックを示す */
} /* /.btn */
.btn:active{ /* 押下(Active)時の触感表現 */
transform:translateY(1px); /* 1pxだけ沈ませて押した感じを出す */
} /* /.btn:active */
/* ===============================================
暗幕レイヤー(背景の半透明カバー)
=============================================== */
.cp-modal .layer{ /* モーダル直下の全画面オーバーレイ */
position:fixed; /* ビューポートに固定配置(スクロールに追従) */
inset:0; /* 上下左右0で全画面を覆う */
background:rgba(2,8,23,.5); /* 濃い紺色系の50%半透明で背景を落とす */
display:grid; /* グリッドで中央寄せを簡易に実現 */
place-items:center; /* 子要素(.dialog)を中央に配置 */
opacity:0; /* 初期は透明で見えない */
pointer-events:none; /* 初期はクリックなどのイベントを遮断 */
transition:.2s; /* opacity の変化を0.2秒でフェード */
z-index:9999; /* ほぼ最前面へ(ヘッダー等より前) */
} /* /.cp-modal .layer */
.cp-modal[data-open="1"] .layer{ /* data-open="1" が付与されたときの状態 */
opacity:1; /* 暗幕をフェードインして可視化 */
pointer-events:auto; /* 背景クリックを有効化(閉じる判定に使用) */
} /* /.cp-modal[data-open="1"] .layer */
/* ===============================================
ダイアログ(モーダル本体カード)
=============================================== */
.cp-modal .dialog{ /* コンテンツを表示する本体カード */
background:#fff; /* 白背景で読みやすく */
color:#0f172a; /* 文字色は濃いインク色でコントラスト良好 */
border-radius:12px; /* 角丸でやわらかい印象 */
padding:20px; /* 内側余白で要素の詰まりを回避 */
width:min(680px,92vw); /* 最大幅680px、モバイルでは画面幅の92% */
max-height:82vh; /* 高さは画面の約8割まで(はみ出し防止) */
overflow:auto; /* 内容が溢れたら内部スクロール */
outline:0; /* 既定のアウトラインは無効(外側で管理) */
box-shadow:0 10px 30px rgba(0,0,0,.2); /* 影で背景から浮かせる */
} /* /.cp-modal .dialog */
/* ===============================================
アニメ対象の初期状態(Bounce 用)
=============================================== */
.anim{ /* .dialog と併用してアニメの初期値を付与 */
opacity:0; /* まずは透明 */
transform:scale(.9); /* 90%サイズから開始して“伸び”の下準備 */
} /* /.anim */ /* 初期:やや小さめから開始 */
/* 表示中のアニメ共通パラメータ(時間・保持・イージング) */
.cp-modal[data-open="1"] .anim{ /* data-open="1" の間だけ再生/保持 */
animation-duration:.6s; /* 0.6秒で再生(弾性を感じる長さ) */
animation-fill-mode:forwards; /* 再生完了後の最終フレームを保持 */
animation-timing-function:cubic-bezier(.2,.7,.2,1); /* 伸び→収束が自然な曲線 */
} /* /.cp-modal[data-open="1"] .anim */
/* ===============================================
キーフレーム(拡大しすぎ→元に収束で“弾む”)
=============================================== */
@keyframes cpBounceIn{ /* Bounce 専用の登場アニメ */
0%{ /* 開始:小さくて透明 */
opacity:0; /* 透明 */
transform:scale(.9); /* 0.9倍 */
} /* /0% */
60%{ /* 途中:少し大きく行き過ぎる */
opacity:1; /* 不透明化済み */
transform:scale(1.03); /* 1.03倍までバウンス(オーバーシュート) */
} /* /60% */
100%{ /* 最終:等倍に落ち着く */
opacity:1; /* 不透明のまま */
transform:scale(1); /* 1.00倍に収束 */
} /* /100% */
} /* /@keyframes cpBounceIn */
/* data-anim="bounce" のモーダルで、表示中のみ上記アニメを適用 */
.cp-modal[data-anim="bounce"][data-open="1"] .anim{
animation-name:cpBounceIn; /* 使用するキーフレームを指定 */
} /* /.cp-modal[data-anim="bounce"][data-open="1"] .anim */
/* ===============================================
モーション軽減(ユーザー設定を尊重)
=============================================== */
@media (prefers-reduced-motion:reduce){ /* 動きを減らす設定が有効な場合 */
.cp-modal[data-open="1"] .anim{ /* アニメ対象要素 */
animation:none; /* アニメーションを無効化 */
opacity:1; /* 即座に表示 */
transform:none; /* 変形を取り消し(等倍で表示) */
} /* /.cp-modal[data-open="1"] .anim */
.cp-modal .layer{ /* 暗幕のフェードも */
transition:none; /* トランジションを無効化して即時切替 */
} /* /.cp-modal .layer */
} /* /@media (prefers-reduced-motion:reduce) */
// Bounce:JS(単体動作版)
(function(){
var modal=document.getElementById('cp-modal-bounce'); if(!modal) return;
var layer=modal.querySelector('.layer'), dialog=modal.querySelector('.dialog');
var openBtn=modal.querySelector('[data-open]'), closeBtn=modal.querySelector('[data-close]');
var lastTrigger=null;
function onKey(e){ if(e.key==='Escape') close(); }
function open(trigger){ lastTrigger=trigger||null; modal.setAttribute('data-open','1'); dialog && dialog.focus(); document.addEventListener('keydown',onKey); }
function close(){ modal.removeAttribute('data-open'); document.removeEventListener('keydown',onKey); if(lastTrigger && lastTrigger.focus) lastTrigger.focus(); }
openBtn && openBtn.addEventListener('click',function(){ open(openBtn); });
closeBtn && closeBtn.addEventListener('click',close);
layer && layer.addEventListener('click',function(e){ if(e.target===layer) close(); });
})();
④ ブラー(Blur:ぼかし解除)
Blur モーダル
ぼかしが徐々に取れながら現れる演出。背景クリック/ESC/「閉じる」で閉じられます。
<!-- Blur:HTML(単体動作版) -->
<div class="cp-modal" id="cp-modal-blur" data-anim="blur">
<button class="btn" type="button" data-open>このアニメで開く</button>
<div class="layer" aria-hidden="true">
<div class="dialog anim" role="dialog" aria-modal="true" aria-labelledby="title-blur" tabindex="-1">
<h3 id="title-blur">Blur モーダル</h3>
<p>フィルタのぼかし解除とフェードで滑らかに表示します。</p>
<button class="btn" type="button" data-close>閉じる</button>
</div>
</div>
</div>
/* ===============================================
ボタン(最小デザイン)
=============================================== */
.btn{ /* モーダル開閉トリガーの共通ボタン */
appearance:none; /* 既定のネイティブ装飾を無効化 */
border:0; /* 枠線なしでフラットに */
border-radius:12px; /* 角丸でタップしやすく */
padding:10px 14px; /* クリック/タップ領域を確保 */
background:#0b6bff; /* アクセントカラー(ブルー) */
color:#fff; /* 白文字でコントラスト確保 */
cursor:pointer; /* ホバー時にポインタ表示 */
} /* /.btn */
.btn:active{ /* 押下状態の視覚フィードバック */
transform:translateY(1px); /* 1pxだけ沈ませて“押した感”を出す */
} /* /.btn:active */
/* ===============================================
暗幕レイヤー(背景の半透明カバー)
=============================================== */
.cp-modal .layer{ /* モーダル直下に敷く全画面オーバーレイ */
position:fixed; /* ビューポートに固定(スクロールしても追従) */
inset:0; /* 上下左右0で全画面を覆う */
background:rgba(2,8,23,.5); /* 濃紺系50%の半透明で背景を落とす */
display:grid; /* 中央配置を簡潔に実装 */
place-items:center; /* 子要素(.dialog)を中央へ */
opacity:0; /* 初期は透明で見えない */
pointer-events:none; /* 初期はクリック不可(背後に透過しない) */
transition:.2s; /* 表示/非表示を0.2秒でフェード */
z-index:9999; /* ほぼ最前面へ(ヘッダー等より前) */
} /* /.cp-modal .layer */
.cp-modal[data-open="1"] .layer{ /* data-open="1" が付いた時の可視状態 */
opacity:1; /* 暗幕をフェードイン */
pointer-events:auto; /* クリックを有効に(背景クリックで閉じる判定) */
} /* /.cp-modal[data-open="1"] .layer */
/* ===============================================
ダイアログ(モーダル本体カード)
=============================================== */
.cp-modal .dialog{ /* コンテンツを載せる白いカード */
background:#fff; /* 白背景で読みやすく */
color:#0f172a; /* 文字色は濃いインク色で視認性UP */
border-radius:12px; /* 角丸でやわらかい印象に */
padding:20px; /* 内側余白で詰まり回避 */
width:min(680px,92vw); /* 最大幅680px、モバイルでは92vwまで */
max-height:82vh; /* 画面高の約8割まで(はみ出し防止) */
overflow:auto; /* 内容が溢れたら内部スクロール */
outline:0; /* 既定のアウトラインは無効(外側で管理) */
box-shadow:0 10px 30px rgba(0,0,0,.2); /* 影で背景から浮かせる */
} /* /.cp-modal .dialog */
/* ===============================================
アニメ対象の初期状態(Blur 用)
=============================================== */
.anim{ /* .dialog と併用してアニメの初期値を設定 */
opacity:0; /* 透明から開始 */
filter:blur(6px); /* ぼかし6pxで“にじみ”状態を作る */
} /* /.anim */ /* 初期:強めのぼかし */
/* 表示中のアニメ共通パラメータ(時間・保持・イージング) */
.cp-modal[data-open="1"] .anim{ /* data-open="1" の間だけ再生 */
animation-duration:.6s; /* 0.6秒で自然な印象に */
animation-fill-mode:forwards; /* 再生後の最終フレームを保持 */
animation-timing-function:cubic-bezier(.2,.7,.2,1); /* 滑らかな加減速 */
} /* /.cp-modal[data-open="1"] .anim */
/* ===============================================
キーフレーム(ぼかし解除しながらフェードイン)
=============================================== */
@keyframes cpBlurIn{ /* Blur 専用の登場アニメ */
from{ /* 開始:にじんで見える */
opacity:0; /* 透明 */
filter:blur(6px); /* 6pxの強いぼかし */
} /* /from */
to{ /* 終了:くっきり見える */
opacity:1; /* 不透明に */
filter:blur(0); /* ぼかしゼロでクリア表示 */
} /* /to */
} /* /@keyframes cpBlurIn */
/* data-anim="blur" が指定されたモーダルに上記アニメを適用 */
.cp-modal[data-anim="blur"][data-open="1"] .anim{
animation-name:cpBlurIn; /* 使用するキーフレームを指定 */
} /* /.cp-modal[data-anim="blur"][data-open="1"] .anim */
/* ===============================================
モーション軽減(ユーザー設定を尊重)
=============================================== */
@media (prefers-reduced-motion:reduce){ /* 動きを減らす設定が有効な場合 */
.cp-modal[data-open="1"] .anim{ /* アニメ対象要素 */
animation:none; /* アニメーションを無効化 */
opacity:1; /* 即座に表示 */
filter:none; /* ぼかしも即座に解除 */
} /* /.cp-modal[data-open="1"] .anim */
.cp-modal .layer{ /* 暗幕のフェードも */
transition:none; /* トランジションを無効化して即切替 */
} /* /.cp-modal .layer */
} /* /@media (prefers-reduced-motion:reduce) */
// Blur:JS(単体動作版)
(function(){
var modal=document.getElementById('cp-modal-blur'); if(!modal) return;
var layer=modal.querySelector('.layer'), dialog=modal.querySelector('.dialog');
var openBtn=modal.querySelector('[data-open]'), closeBtn=modal.querySelector('[data-close]');
var lastTrigger=null;
function onKey(e){ if(e.key==='Escape') close(); }
function open(trigger){ lastTrigger=trigger||null; modal.setAttribute('data-open','1'); dialog && dialog.focus(); document.addEventListener('keydown',onKey); }
function close(){ modal.removeAttribute('data-open'); document.removeEventListener('keydown',onKey); if(lastTrigger && lastTrigger.focus) lastTrigger.focus(); }
openBtn && openBtn.addEventListener('click',function(){ open(openBtn); });
closeBtn && closeBtn.addEventListener('click',close);
layer && layer.addEventListener('click',function(e){ if(e.target===layer) close(); });
})();
⑤ スキュー(Skew:斜めに)
Skew モーダル
やや斜めの状態から正対に整いながら現れます。背景クリック/ESC/「閉じる」で閉じられます。
<!-- Skew:HTML(単体動作版) -->
<div class="cp-modal" id="cp-modal-skew" data-anim="skew">
<button class="btn" type="button" data-open>このアニメで開く</button>
<div class="layer" aria-hidden="true">
<div class="dialog anim" role="dialog" aria-modal="true" aria-labelledby="title-skew" tabindex="-1">
<h3 id="title-skew">Skew モーダル</h3>
<p>skewY で斜めに入ってからまっすぐに整います。ESC/背景クリック/ボタンで閉じます。</p>
<button class="btn" type="button" data-close>閉じる</button>
</div>
</div>
</div>
/* ===============================================
ボタン(最小デザイン)
=============================================== */
.btn{ /* モーダル開閉のトリガーボタン */
appearance:none; /* 既定のOS/ブラウザ装飾を打ち消す */
border:0; /* 枠線を消してフラットに */
border-radius:12px; /* 角丸でタップしやすく */
padding:10px 14px; /* クリック/タップ領域を確保 */
background:#0b6bff; /* アクセントカラー */
color:#fff; /* 白文字でコントラスト確保 */
cursor:pointer; /* ホバー時にポインタ表示 */
} /* /.btn */
.btn:active{ /* 押下時の軽いフィードバック */
transform:translateY(1px); /* 1px沈ませて“押した感”を出す */
} /* /.btn:active */
/* ===============================================
暗幕レイヤー(背景の半透明カバー)
=============================================== */
.cp-modal .layer{ /* モーダルの全画面オーバーレイ */
position:fixed; /* ビューポートに固定配置 */
inset:0; /* 上下左右0で全画面を覆う */
background:rgba(2,8,23,.5); /* 濃紺系の50%透過で背景を落とす */
display:grid; /* 中央配置を簡潔に実現 */
place-items:center; /* 子要素(.dialog)を中央へ */
opacity:0; /* 初期は非表示(透明) */
pointer-events:none; /* 初期はクリック不可で背後へ透過しない */
transition:.2s; /* フェード切り替え時間 */
z-index:9999; /* ほぼ最前面に表示 */
} /* /.cp-modal .layer */
.cp-modal[data-open="1"] .layer{ /* data-open="1" 付与時の可視状態 */
opacity:1; /* フェードインで表示 */
pointer-events:auto; /* クリックを有効化(背景クリックで閉じる判定) */
} /* /.cp-modal[data-open="1"] .layer */
/* ===============================================
ダイアログ(モーダル本体)
=============================================== */
.cp-modal .dialog{ /* 中身を載せる白いカード */
background:#fff; /* 白背景で読みやすく */
color:#0f172a; /* 濃い文字色で視認性UP */
border-radius:12px; /* 角丸で柔らかい印象に */
padding:20px; /* 内側余白 */
width:min(680px,92vw); /* 最大幅 680px、モバイルは 92vw まで */
max-height:82vh; /* 画面高の約8割に制限(はみ出し防止) */
overflow:auto; /* 溢れたら内部スクロール */
outline:0; /* 既定アウトラインは無効(外側で管理) */
box-shadow:0 10px 30px rgba(0,0,0,.2); /* 影で浮かせる */
} /* /.cp-modal .dialog */
/* ===============================================
アニメ対象の初期状態(Skew 用)
=============================================== */
.anim{ /* .dialog に併用してアニメ初期値を設定 */
opacity:0; /* 透明から開始 */
transform:skewY(8deg); /* 縦方向に8度ほど傾けておく */
} /* /.anim */ /* 初期:やや斜傾 */
/* 表示中のアニメ共通パラメータ(時間・保持・イージング) */
.cp-modal[data-open="1"] .anim{ /* data-open="1" の間だけ再生 */
animation-duration:.6s; /* 0.6秒で自然な印象に */
animation-fill-mode:forwards; /* 再生後の最終フレームを保持 */
animation-timing-function:cubic-bezier(.2,.7,.2,1); /* 滑らかな加減速 */
} /* /.cp-modal[data-open="1"] .anim */
/* ===============================================
キーフレーム(斜傾 → 正対へ整う)
=============================================== */
@keyframes cpSkewIn{ /* Skew 専用の登場アニメ */
from{ /* 開始:傾いたまま、かつ透明 */
opacity:0; /* 透明 */
transform:skewY(8deg); /* Y軸方向に8度傾ける */
} /* /from */
to{ /* 終了:正対し、不透明に */
opacity:1; /* 不透明化 */
transform:skewY(0); /* 傾きを 0 に戻す(正対) */
} /* /to */
} /* /@keyframes cpSkewIn */
/* data-anim="skew" のモーダルで上記キーフレームを適用 */
.cp-modal[data-anim="skew"][data-open="1"] .anim{
animation-name:cpSkewIn; /* 使用するアニメ名を指定 */
} /* /.cp-modal[data-anim="skew"][data-open="1"] .anim */
/* ===============================================
モーション軽減(ユーザー設定を尊重)
=============================================== */
@media (prefers-reduced-motion:reduce){ /* 動きを減らす設定が有効な場合 */
.cp-modal[data-open="1"] .anim{ /* アニメ対象要素 */
animation:none; /* アニメーションを無効化して */
opacity:1; /* 即座に表示 */
transform:none; /* 傾きも即座に解除(正対表示) */
} /* /.cp-modal[data-open="1"] .anim */
.cp-modal .layer{ /* 暗幕のフェードも */
transition:none; /* トランジションを無効化して即切替 */
} /* /.cp-modal .layer */
} /* /@media (prefers-reduced-motion:reduce) */
// Skew:JS(単体動作版)
(function(){
var modal=document.getElementById('cp-modal-skew'); if(!modal) return;
var layer=modal.querySelector('.layer'), dialog=modal.querySelector('.dialog');
var openBtn=modal.querySelector('[data-open]'), closeBtn=modal.querySelector('[data-close]');
var lastTrigger=null;
function onKey(e){ if(e.key==='Escape') close(); }
function open(trigger){ lastTrigger=trigger||null; modal.setAttribute('data-open','1'); dialog && dialog.focus(); document.addEventListener('keydown',onKey); }
function close(){ modal.removeAttribute('data-open'); document.removeEventListener('keydown',onKey); if(lastTrigger && lastTrigger.focus) lastTrigger.focus(); }
openBtn && openBtn.addEventListener('click',function(){ open(openBtn); });
closeBtn && closeBtn.addEventListener('click',close);
layer && layer.addEventListener('click',function(e){ if(e.target===layer) close(); });
})();
コメント