No.190
MODAL CSSJS

Dialog Light Dismiss

dialog の closedby 属性を any・closerequest・none に切り替えて、外側を押す・Esc を押すでダイアログが閉じるかどうかを試せるデモ。閉じた方法を記録し、未対応のブラウザでは同じ動きを JavaScript で補います。

MY COLLECTION RANDOM ↗

DEMO

closedby の値を選んで開き、外側を押す・Esc を押す・ボタンで閉じる を試してください

    closedby="any"

    CODE

    HTML
    <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>
    CSS
    .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; }
    }
    JS
    (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

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

    📖 なかしまぁ先生の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アソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。

    CREATOR TOOLS · スクール(PR)

    🎬 Adobe Creative Cloud — After Effects / Photoshop 業界標準ツール → 💻 SkillHacks — プログラミング・動画編集オンラインスクール → 🚀 デイトラ — Web制作スクール・実案件レベルまで習得 →

    ※本サイトはアフィリエイトリンクを含みます。リンク経由のご購入・お申し込みで運営者が紹介料を受け取る場合があります。

    RELATED EFFECTS

    ↗ MORE
    → ALERT DIALOG SHAKE → MODAL SCALE BLUR → BOTTOM SHEET

    closedby(ダイアログの閉じ方の指定)とは

    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プロンプトは、クレジット表記なしで自由に使えます。コピーしてそのままプロジェクトに利用できます。