DEMO
closedby の値を選んで開き、外側を押す・Esc を押す・ボタンで閉じる を試してください
CODE
<div class="demo-stage dld-stage">
<p class="dld-lead">closedby の値を選んで開き、外側を押す・Esc を押す・ボタンで閉じる を試してください</p>
<div class="dld-bar">
<button type="button" class="dld-open" data-mode="any">any で開く</button>
<button type="button" class="dld-open" data-mode="closerequest">closerequest で開く</button>
<button type="button" class="dld-open" data-mode="none">none で開く</button>
</div>
<ol class="dld-log" id="dld-log" aria-live="polite"></ol>
<dialog class="dld-dialog" id="dld-dialog" aria-labelledby="dld-title">
<h3 class="dld-title" id="dld-title">closedby="<span id="dld-mode">any</span>"</h3>
<p class="dld-desc" id="dld-desc"></p>
<p class="dld-note" id="dld-note" aria-live="polite"></p>
<button type="button" class="dld-close" id="dld-close">閉じる</button>
</dialog>
</div>
.dld-stage { position:relative; overflow:hidden; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:.9rem; padding:2rem 1.25rem; background:#0B0B10; min-height:320px; }
.dld-lead { margin:0; max-width:420px; color:#C9C9D1; font-size:.82rem; line-height:1.7; text-align:center; }
.dld-bar { display:flex; gap:.5rem; flex-wrap:wrap; justify-content:center; }
.dld-open { min-height:44px; padding:0 1rem; border-radius:10px; border:1px solid rgba(255,255,255,.22); background:transparent; color:#EDEDED; font:inherit; font-size:.84rem; cursor:pointer; font-family:ui-monospace,monospace; }
.dld-open:focus-visible, .dld-close:focus-visible { outline:2px solid #fff; outline-offset:3px; }
.dld-log { margin:0; padding:0; list-style:none; min-height:4.4em; width:100%; max-width:360px; color:#A1A1AA; font-size:.78rem; line-height:1.7; }
.dld-log li { animation: dld-in .3s ease-out; }
.dld-log li:first-child { color:#F9A8D4; }
@keyframes dld-in { from { opacity:0; transform:translateY(-4px); } }
.dld-dialog { margin:auto; width:min(320px, calc(100% - 2rem)); box-sizing:border-box; padding:1.2rem; border:1px solid rgba(255,255,255,.12); border-radius:14px; background:#1E1E26; color:#EDEDED;
opacity:0; transform:scale(.94);
transition: opacity .22s, transform .22s cubic-bezier(.22,1,.36,1), overlay .22s allow-discrete, display .22s allow-discrete; }
.dld-dialog[open] { opacity:1; transform:none; }
@starting-style { .dld-dialog[open] { opacity:0; transform:scale(.94); } }
.dld-dialog::backdrop { background:rgb(0 0 0 / 0); transition: background-color .22s, overlay .22s allow-discrete, display .22s allow-discrete; }
.dld-dialog[open]::backdrop { background:rgb(0 0 0 / .6); }
@starting-style { .dld-dialog[open]::backdrop { background:rgb(0 0 0 / 0); } }
.dld-dialog.is-nudge { animation: dld-nudge .3s ease; }
@keyframes dld-nudge { 50% { transform:scale(1.03); } }
.dld-title { margin:0 0 .4rem; color:#fff; font-size:.98rem; font-family:ui-monospace,monospace; }
.dld-desc { margin:0; color:#C9C9D1; font-size:.8rem; line-height:1.7; }
.dld-note { margin:.4rem 0 0; min-height:1.3em; color:#FCA5A5; font-size:.76rem; }
.dld-close { margin-top:.8rem; min-height:44px; padding:0 1.1rem; border:0; border-radius:10px; background:#D1177A; color:#fff; font:inherit; font-weight:600; font-size:.86rem; cursor:pointer; }
@media (prefers-reduced-motion: reduce) {
.dld-dialog, .dld-dialog::backdrop { transition:none; }
.dld-dialog.is-nudge, .dld-log li { animation:none; }
}
(function(){
var dlg = document.getElementById('dld-dialog');
var log = document.getElementById('dld-log');
var modeEl = document.getElementById('dld-mode');
var desc = document.getElementById('dld-desc');
var note = document.getElementById('dld-note');
var closeBtn = document.getElementById('dld-close');
if (!dlg || !log || !modeEl || !desc || !note || !closeBtn) return;
var native = 'closedBy' in HTMLDialogElement.prototype;
var DESC = { any: '外側を押す・Esc・ボタンのどれでも閉じます。', closerequest: 'Esc とボタンで閉じます。外側を押しても閉じません。', none: 'ボタンでしか閉じません。Esc でも外側でも閉じません。' };
var mode = 'any', reason = '';
function add(t){
var li = document.createElement('li'); li.textContent = t;
log.insertBefore(li, log.firstChild);
while (log.children.length > 3) log.lastChild.remove();
}
function nudge(msg){
note.textContent = msg;
dlg.classList.remove('is-nudge'); void dlg.offsetWidth; dlg.classList.add('is-nudge');
}
function outside(e){
var r = dlg.getBoundingClientRect();
return e.target === dlg && (e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom);
}
document.querySelectorAll('.dld-open').forEach(function(b){
b.addEventListener('click', function(){
mode = b.dataset.mode; reason = '';
dlg.setAttribute('closedby', mode);
modeEl.textContent = mode; desc.textContent = DESC[mode]; note.textContent = '';
dlg.showModal();
});
});
closeBtn.addEventListener('click', function(){ reason = 'ボタン'; dlg.close(); });
dlg.addEventListener('keydown', function(e){ if (e.key === 'Escape') reason = 'Esc'; });
dlg.addEventListener('pointerdown', function(e){ if (outside(e)) reason = '外側'; });
dlg.addEventListener('click', function(e){
if (!outside(e)) return;
/* closedby 未対応のブラウザでは any の時だけ外側クリックで閉じる(light dismiss の代わり) */
if (!native && mode === 'any') { dlg.close(); return; }
setTimeout(function(){ if (dlg.open) nudge('この設定では外側を押しても閉じません'); }, 0);
});
dlg.addEventListener('cancel', function(e){
if (!native && mode === 'none') e.preventDefault(); /* 未対応ブラウザで none を再現 */
setTimeout(function(){ if (dlg.open && reason === 'Esc') nudge('この設定では Esc でも閉じません'); }, 0);
});
document.addEventListener('keydown', function(e){
if (e.key === 'Escape' && dlg.open && mode === 'none') setTimeout(function(){ if (dlg.open) nudge('この設定では Esc でも閉じません'); }, 0);
});
dlg.addEventListener('animationend', function(){ dlg.classList.remove('is-nudge'); });
dlg.addEventListener('close', function(){
dlg.classList.remove('is-nudge'); /* 残すと次に開いた瞬間にふくらみが再生される */
add('closedby="' + mode + '" → ' + (reason || 'その他の方法') + 'で閉じました');
});
})();
AI PROMPT
dialog の closedby 属性の違いを試せるデモを作って。「any で開く」「closerequest で開く」「none で開く」の3つのボタンを置き、押したボタンの値を dialog に setAttribute('closedby', 値) してから showModal() で開く(ページ側の * { margin:0 } のようなリセットで UA の margin:auto が消えると dialog が左上に張り付くので、dialog に margin:auto を付け直す)。any は外側を押す(light dismiss)・Esc・閉じるボタンのどれでも閉じる、closerequest は Esc と閉じるボタンだけ、none は閉じるボタンだけで閉じる。何で閉じたかを記録するため、dialog の keydown で Escape、pointerdown で「押した位置が dialog の getBoundingClientRect の外で、かつ target が dialog 自身」なら外側、閉じるボタンなら「ボタン」を覚えておき、close イベントで「closedby="any" → 外側で閉じました」のように最新3件をリストの先頭に追加する。閉じなかった操作(外側クリックや Esc)の時は、0秒の setTimeout の後でまだ open なら「この設定では外側を押しても閉じません」と表示してダイアログを小さく1回ふくらませる。'closedBy' in HTMLDialogElement.prototype が false のブラウザでは、any の時だけ外側クリックで close() し、none の時は cancel イベントを preventDefault して Esc で閉じないようにする。ダイアログの開閉は opacity と transform の transition、@starting-style、overlay と display の allow-discrete で付け、::backdrop も暗くする。prefers-reduced-motion: reduce の時は transition とアニメーションを無効化する。
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アソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。
CREATOR TOOLS · スクール(PR)
※本サイトはアフィリエイトリンクを含みます。リンク経由のご購入・お申し込みで運営者が紹介料を受け取る場合があります。
closedby は、dialog 要素をどんな操作で閉じられるかを決める HTML の属性です。MDN では閉じ方を、外側を押す「light dismiss」、Esc キーやスマートフォンの戻る操作のような「プラットフォームの操作」、閉じるボタンのような「開発者が用意した方法」の3つに分け、any はその全て、closerequest は後ろの2つ、none は開発者が用意した方法だけで閉じると説明しています。以前は外側を押して閉じるために、クリック位置を調べる JavaScript を書く必要がありました。
当サイトの Alert Dialog Shake は外側を押しても閉じずに左右へ揺れて知らせる確認ダイアログ、Modal Scale Blur は背景をぼかして弾むように開くモーダル、Bottom Sheet は画面の下からせり上がるシートです。本ページはダイアログの見た目ではなく「どの操作で閉じるか」を属性で選ぶ別テーマです。
お知らせや画像の拡大表示のように、どう閉じても困らないダイアログは any にすると使いやすくなります。入力途中のフォームや削除の確認のように、うっかり閉じると困るダイアログは closerequest や none にして、閉じる操作をはっきりさせておくのが安全です。
closedby を書かない時はどう動きますか?
MDN の説明では、closedby が無い(または不正な値の)時は、showModal() で開いたダイアログは closedby="closerequest" と同じ扱いになり、Esc では閉じますが外側を押しても閉じません。それ以外の方法で開いた時は none と同じ扱いです。外側を押して閉じたい時は any を指定します。
closedby に対応していないブラウザではどうなりますか?
MDN の互換性データ(browser-compat-data 2026-09-24 版)では、closedby は Chrome・Edge 134、Firefox 141 から使えますが、Safari は開発版(Technology Preview)のみで、iOS の Safari は未対応です。未対応のブラウザでは属性が無視され、showModal() の既定どおり Esc だけで閉じます。本実装は 'closedBy' in HTMLDialogElement.prototype で対応を調べ、未対応の時だけ any の外側クリックと none の Esc を JavaScript で再現しています。
コードは無料で使えますか?
はい。Motion LabのコードスニペットとAIプロンプトは、クレジット表記なしで自由に使えます。コピーしてそのままプロジェクトに利用できます。