モーダルの開き方 5種類
モーダルの開き方を5種類用意しました。「コードブロック(HTML/CSS/JS)」内をコピペしてそのまま使えます。
コードタブの中身は 単体ファイルでそのまま動く版(.cp-article
スコープなし)です。
(Fade(ふわっと)/Slide(スライドアップ)/Zoom(ズームイン)/Flip(フリップ)/Rotate(ローテート))
コピー時のポイント
CSS/JS はそのまま外部ファイル化も可能です。HTML の id(例:cp-modal-fade
)と JS 内の参照が一致していることをご確認ください。
コードについて
本記事のコードは AI(ChatGPT)による生成をベースに作成・調整しています。ご利用の環境でテストの上ご使用ください。
免責
本コードの利用に伴う不具合・損害について、当サイトは責任を負いません。自己責任にてご利用ください。
① ふわっと(Fade)
Fade モーダル
ふわっとフェードインで表示します。背景クリック/ESC/「閉じる」で閉じられます。
<!-- Fade モーダル:HTML(単体動作) -->
<div class="cp-modal" id="cp-modal-fade" data-anim="fade">
<!-- トリガー:クリックで open() -->
<button class="btn" type="button" data-open>このアニメで開く</button>
<!-- 背景の暗幕:非表示時は CSS でクリック不可 -->
<div class="layer" aria-hidden="true">
<!-- 本体ダイアログ:.anim を付けてフェード対象にする -->
<div class="dialog anim" role="dialog" aria-modal="true" aria-labelledby="title-fade" tabindex="-1">
<!-- タイトル(スクリーンリーダー関連付け) -->
<h3 id="title-fade">Fade モーダル</h3>
<!-- 本文:説明など任意 -->
<p>ふわっとフェードインで表示します。背景クリック/ESC/「閉じる」で閉じられます。</p>
<!-- 閉じるボタン -->
<button class="btn" type="button" data-close>閉じる</button>
</div> <!-- /.dialog -->
</div> <!-- /.layer -->
</div> <!-- /.cp-modal -->
/* ===============================================
ボタン(最小デザイン)
=============================================== */
.btn{
appearance:none; /* ブラウザ既定の見た目を打ち消す */
border:0; /* 枠線なし */
border-radius:12px; /* 角丸 */
padding:10px 14px; /* クリック領域 */
background:#0b6bff; /* アクセント色 */
color:#fff; /* 文字色 */
cursor:pointer; /* カーソル */
}
.btn:active{ transform:translateY(1px) } /* 押下感 */
/* ===============================================
モーダルの土台
=============================================== */
.cp-modal .layer{
position:fixed; inset:0; /* 全画面覆う */
background:rgba(2,8,23,.5); /* 半透明の暗幕 */
display:grid; place-items:center; /* ダイアログを中央配置 */
opacity:0; pointer-events:none; /* 初期は見えず操作不可 */
transition:.2s; z-index:9999; /* フェード&前面 */
}
.cp-modal[data-open="1"] .layer{
opacity:1; pointer-events:auto; /* 表示時は可視&クリック可 */
}
/* ダイアログ本体 */
.cp-modal .dialog{
background:#fff; color:#0f172a; /* 白背景+読みやすい文字色 */
border-radius:12px; padding:20px; /* カード風 */
width:min(680px,92vw); max-height:82vh; /* 画面収まり */
overflow:auto; outline:0; /* スクロール&フォーカス枠は外側で */
box-shadow:0 10px 30px rgba(0,0,0,.2); /* 影 */
}
/* アニメーション対象の初期状態 */
.anim{ opacity:0; transform:translateY(0) scale(1) rotate(0) rotateX(0) }
/* 表示中(data-open=1)のときアニメを走らせる */
.cp-modal[data-open="1"] .anim{
animation-duration:.5s;
animation-fill-mode:forwards; /* 再生後の見た目を保持 */
animation-timing-function:cubic-bezier(.2,.7,.2,1); /* 滑らか */
}
/* キーフレーム:フェードイン(透明→不透明) */
@keyframes cpFadeIn{ from{opacity:0} to{opacity:1} }
/* data-anim="fade" の時に適用されるアニメ名 */
.cp-modal[data-anim="fade"][data-open="1"] .anim{ animation-name:cpFadeIn }
/* モーション軽減(ユーザ設定尊重) */
@media (prefers-reduced-motion:reduce){
.cp-modal[data-open="1"] .anim{ animation:none; opacity:1; transform:none }
.cp-modal .layer{ transition:none }
}
(function(){
/* ルート取得(id は HTML と合わせる) */
var modal=document.getElementById('cp-modal-fade'); 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;
/* ESC キーで閉じるハンドラ */
function onKey(e){ if(e.key==='Escape') close(); }
/* 開く:data-open="1" で表示+フォーカスをダイアログへ */
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(); /* 背景クリックのみで閉じる */
});
})();
② スライドアップ(Slide)
Slide モーダル
下からスライドして表示します。背景クリック/ESC/「閉じる」で閉じられます。
<!-- Slide モーダル:HTML(単体動作) -->
<div class="cp-modal" id="cp-modal-slide" data-anim="slide">
<!-- トリガー -->
<button class="btn" type="button" data-open>このアニメで開く</button>
<!-- 背景(クリックで閉じる判定に使用) -->
<div class="layer" aria-hidden="true">
<!-- 本体:.anim がアニメ対象、aria-labelledby で見出し関連付け -->
<div class="dialog anim" role="dialog" aria-modal="true" aria-labelledby="title-slide" tabindex="-1">
<h3 id="title-slide">Slide モーダル</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:active{transform:translateY(1px)}
/* 背景レイヤー:フェードで表示切替 */
.cp-modal .layer{
position:fixed; inset:0; background:rgba(2,8,23,.5);
display:grid; place-items:center; opacity:0; pointer-events:none;
transition:.2s; z-index:9999
}
.cp-modal[data-open="1"] .layer{ opacity:1; pointer-events:auto }
/* ダイアログの外観(共通) */
.cp-modal .dialog{
background:#fff; color:#0f172a; border-radius:12px; padding:20px;
width:min(680px,92vw); max-height:82vh; overflow:auto; outline:0;
box-shadow:0 10px 30px rgba(0,0,0,.2)
}
/* アニメ初期:透明(他アニメの影響を受けないよう transform は既定に) */
.anim{ opacity:0; transform:translateY(0) scale(1) rotate(0) rotateX(0) }
/* 表示中のアニメ設定(共通) */
.cp-modal[data-open="1"] .anim{
animation-duration:.5s; animation-fill-mode:forwards;
animation-timing-function:cubic-bezier(.2,.7,.2,1)
}
/* 下から持ち上がる(+16px → 0px) */
@keyframes cpSlideUp{ from{opacity:0; transform:translateY(16px)} to{opacity:1; transform:translateY(0)} }
/* data-anim=slide の時に適用 */
.cp-modal[data-anim="slide"][data-open="1"] .anim{ animation-name:cpSlideUp }
/* モーション軽減 */
@media (prefers-reduced-motion:reduce){
.cp-modal[data-open="1"] .anim{ animation:none; opacity:1; transform:none }
.cp-modal .layer{ transition:none }
}
(function(){
var modal=document.getElementById('cp-modal-slide'); 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(); });
})();
③ ズームイン(Zoom)
Zoom モーダル
少し縮小から拡大して表示します。背景クリック/ESC/「閉じる」で閉じられます。
<!-- Zoom モーダル:HTML(単体動作) -->
<div class="cp-modal" id="cp-modal-zoom" data-anim="zoom">
<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-zoom" tabindex="-1">
<h3 id="title-zoom">Zoom モーダル</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:active{transform:translateY(1px)}
/* 背景:共通フェード表示 */
.cp-modal .layer{
position:fixed; inset:0; background:rgba(2,8,23,.5);
display:grid; place-items:center; opacity:0; pointer-events:none;
transition:.2s; z-index:9999
}
.cp-modal[data-open="1"] .layer{ opacity:1; pointer-events:auto }
/* ダイアログ外観 */
.cp-modal .dialog{
background:#fff; color:#0f172a; border-radius:12px; padding:20px;
width:min(680px,92vw); max-height:82vh; overflow:auto; outline:0;
box-shadow:0 10px 30px rgba(0,0,0,.2)
}
/* アニメ初期化(透明) */
.anim{ opacity:0; transform:translateY(0) scale(1) rotate(0) rotateX(0) }
/* 再生パラメータ(共通) */
.cp-modal[data-open="1"] .anim{
animation-duration:.5s; animation-fill-mode:forwards;
animation-timing-function:cubic-bezier(.2,.7,.2,1)
}
/* 0.92 → 1.00 へ拡大しつつ不透明化 */
@keyframes cpZoomIn{ from{opacity:0; transform:scale(.92)} to{opacity:1; transform:scale(1)} }
/* data-anim=zoom に適用 */
.cp-modal[data-anim="zoom"][data-open="1"] .anim{ animation-name:cpZoomIn }
/* モーション軽減 */
@media (prefers-reduced-motion:reduce){
.cp-modal[data-open="1"] .anim{ animation:none; opacity:1; transform:none }
.cp-modal .layer{ transition:none }
}
(function(){
var modal=document.getElementById('cp-modal-zoom'); 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(); });
})();
④ フリップ(Flip)
Flip モーダル
Y軸方向にフリップしながら表示します。背景クリック/ESC/「閉じる」で閉じられます。
<!-- Flip モーダル:HTML(単体動作) -->
<div class="cp-modal" id="cp-modal-flip" data-anim="flip">
<!-- 開くトリガー -->
<button class="btn" type="button" data-open>このアニメで開く</button>
<div class="layer" aria-hidden="true">
<!-- .anim が 3D 回転の対象。tabindex でフォーカス可 -->
<div class="dialog anim" role="dialog" aria-modal="true" aria-labelledby="title-flip" tabindex="-1">
<h3 id="title-flip">Flip モーダル</h3>
<p>Y軸方向にフリップしながら表示します。背景クリック/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:active{transform:translateY(1px)}
/* 暗幕(共通) */
.cp-modal .layer{
position:fixed; inset:0; background:rgba(2,8,23,.5);
display:grid; place-items:center; opacity:0; pointer-events:none;
transition:.2s; z-index:9999
}
.cp-modal[data-open="1"] .layer{ opacity:1; pointer-events:auto }
/* ダイアログ(共通) */
.cp-modal .dialog{
background:#fff; color:#0f172a; border-radius:12px; padding:20px;
width:min(680px,92vw); max-height:82vh; overflow:auto; outline:0;
box-shadow:0 10px 30px rgba(0,0,0,.2)
}
/* アニメ初期値 */
.anim{ opacity:0; transform:translateY(0) scale(1) rotate(0) rotateX(0) }
/* 再生(共通) */
.cp-modal[data-open="1"] .anim{
animation-duration:.5s; animation-fill-mode:forwards;
animation-timing-function:cubic-bezier(.2,.7,.2,1)
}
/* Y軸回転でフリップイン */
@keyframes cpFlipIn{ from{opacity:0; transform:rotateX(-70deg)} to{opacity:1; transform:rotateX(0)} }
/* 3D らしさ:transform-style を有効化しつつ適用 */
.cp-modal[data-anim="flip"][data-open="1"] .anim{
transform-style:preserve-3d;
animation-name:cpFlipIn
}
/* モーション軽減 */
@media (prefers-reduced-motion:reduce){
.cp-modal[data-open="1"] .anim{ animation:none; opacity:1; transform:none }
.cp-modal .layer{ transition:none }
}
(function(){
var modal=document.getElementById('cp-modal-flip'); 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(); });
})();
⑤ ローテート(Rotate)
Rotate モーダル
軽い回転+拡大で表示します。背景クリック/ESC/「閉じる」で閉じられます。
<!-- Rotate モーダル:HTML(単体動作) -->
<div class="cp-modal" id="cp-modal-rotate" data-anim="rotate">
<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-rotate" tabindex="-1">
<h3 id="title-rotate">Rotate モーダル</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:active{transform:translateY(1px)}
/* 暗幕(共通) */
.cp-modal .layer{
position:fixed; inset:0; background:rgba(2,8,23,.5);
display:grid; place-items:center; opacity:0; pointer-events:none;
transition:.2s; z-index:9999
}
.cp-modal[data-open="1"] .layer{ opacity:1; pointer-events:auto }
/* ダイアログ(共通) */
.cp-modal .dialog{
background:#fff; color:#0f172a; border-radius:12px; padding:20px;
width:min(680px,92vw); max-height:82vh; overflow:auto; outline:0;
box-shadow:0 10px 30px rgba(0,0,0,.2)
}
/* アニメ初期値 */
.anim{ opacity:0; transform:translateY(0) scale(1) rotate(0) rotateX(0) }
/* 再生共通 */
.cp-modal[data-open="1"] .anim{
animation-duration:.5s; animation-fill-mode:forwards;
animation-timing-function:cubic-bezier(.2,.7,.2,1)
}
/* 少し縮小+回転から、等倍+回転 0 に戻す */
@keyframes cpRotateIn{
from{ opacity:0; transform:scale(.9) rotate(-6deg) }
to { opacity:1; transform:scale(1) rotate(0) }
}
/* data-anim=rotate に適用 */
.cp-modal[data-anim="rotate"][data-open="1"] .anim{ animation-name:cpRotateIn }
/* モーション軽減 */
@media (prefers-reduced-motion:reduce){
.cp-modal[data-open="1"] .anim{ animation:none; opacity:1; transform:none }
.cp-modal .layer{ transition:none }
}
(function(){
var modal=document.getElementById('cp-modal-rotate'); 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(); });
})();
コメント