dialog 要素を利用することで 最小限の JavaScript でモーダルウィンドウを作成することが可能です。dialog 要素をこれまで使った経験がない人であれば dialog 要素を利用することでこんなことができるのかと驚きがあるかもしれません。本文書は dialog 要素の基本的な機能と使い方を確認してシンプルなコードでモーダルウィンドウを作成していきます。

Dialog の設定

はじめての Dialog タグ

Dialog 要素のみでどのようなことができるのか確認するために任意の場所に index.html ファイルを作成します。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>dialog element</title>
  </head>
  <body>
    <dialog>
      <h2>Dialog</h2>
    </dialog>
  </body>
</html>

dialog 要素の中に h2 タグで Dialog という文字列を設定していますが index.html ファイルをブラウザで開いても画面上には何も表示されません。

何も表示されない初期画面
何も表示されない初期画面

dialog 要素に open 属性を追加します。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>dialog element</title>
  </head>
  <body>
    <dialog open>
      <h2>Dialog</h2>
    </dialog>
  </body>
</html>

dialog 要素に open 属性を追加しただけで、border で囲まれた Dialog という文字列が表示されます。

open属性を追加
open属性を追加

dialog 要素に open 属性をつけない場合は何も表示されず、open 属性を追加すると dialog 要素の中に設定したコンテンツが表示されることが確認できました。

これだけの機能であれば何もメリットはありませんが、モーダルウィンドウであれば表示・非表示を切り替えられる機能が必要です。JavaScript を追加してモーダルウィンドウを作成するために必要な機能を追加していきます。

JavaScript の追加

show メソッド

Dialog 要素の外側に Dialog を表示する際にクリックする”Open”ボタンを追加して Dialog 要素の内側に非表示にするための”Close”ボタンを追加します。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>dialog element</title>
  </head>
  <body>
    <button class="openBtn">Open</button>
    <dialog>
      <h2>Dialog</h2>
      <button class="closeBtn">Close</button>
    </dialog>
  </body>
</html>

ボタンを追加しただけなのでボタンをクリックしても何も変化はありません。

ボタン要素の追加
ボタン要素の追加

JavaScript の document.querySelector を利用して dialog 要素と button 要素を取得して button 要素にクリックイベントを設定し、ボタンをクリックすると dialog 要素が持っている show メソッドを実行します。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>dialog element</title>
  </head>
  <body>
    <button class="openBtn">Open</button>
    <dialog>
      <h2>Dialog</h2>
      <button class="closeBtn">Close</button>
    </dialog>
    <script>
      const dialog = document.querySelector('dialog');
      const openBtn = document.querySelector('.openBtn');

      openBtn.addEventListener('click', () => {
        dialog.show();
      });
    </script>
  </body>
</html>

“Open”ボタンをクリックすると ブラウザ上に Dialog が表示されるようになりました。

showメソッドでdialogが表示
showメソッドでdialogが表示

デベロッパーツールで要素を確認すると show メソッドを実行することで dialog 要素に open 属性が追加されていることがわかります。dialog が非表示の時は CSS の display プロパティは”none”に設定されていますがボタンをクリックすると以下の CSS のスタイルの設定により Dialog が表示されるようになります。つまり display プロパティの”none”と”block”によって非表示から表示に変わっていることがわかります。


dialog[open] {
    display: block;
}

show メソッドでは open 属性が dialog 要素に追加されますが show メソッドを利用せず JavaScript によって open 属性を追加することでも Dialog が表示されます。下記では setAttribute メソッドで属性 open を dialog 要素に追加しています。


openBtn.addEventListener('click', () => {
  dialog.setAttribute('open', '');
});
open 属性を設定するよりも show メソッドや showModal メソッドを利用することが推奨されています。

showModal メソッド

show メソッドの代わりに今度は showModal メソッドを利用します。showModal メソッドも Dialog 要素が持っているメソッドです。showModal を実行すると名前に Modal がついているようにモーダルウィンドウのように背景が追加されます。


<script>
    const dialog = document.querySelector('dialog');
    const openBtn = document.querySelector('.openBtn');

    openBtn.addEventListener('click', () => {
    dialog.showModal();
    });
</script>
showModalメソッド
showModalメソッド

ブラウザのデベロッパーツールを利用して要素の確認を行います。top-layer と backdrop という文字列を確認することができます。要素の重なりを設定する際に z-index を利用して要素の重なりを設定することができますが top-layer は z-index を設定しなくても一番上の Layer として表示されるので Dialog が表示された場合に下の Layer にアクセスすることができせん。ここでは”Open”ボタンは表示されている Dialog の下のレイヤーになるためアクセスすることはできません。backdrop は背景色を設定するために利用することができます。デフォルトでは薄いグレーが設定されています。

要素の確認
要素の確認

show メソッドを利用した場合は top-layer は設定されないので Dialog が表示されても”open”ボタンをクリックすることができます。確認した通り show メソッドでは backdrop が設定されることもありません。open 属性を設定した場合も同様です。

Escape キー

showModal で Dialog を表示した状態で Escape キーを押してしてください。Escape キーを押すと表示されていた Dialog が非表示になります。

show メソッドで開いた Dialog は Escape キーを押しても非表示になることはありません。

close メソッド

Dialog を非表示にしたい場合には close メソッドを利用することができます。close メソッドを実行すると表示されていた Dialog は非表示となります。


<script>
    const dialog = document.querySelector('dialog');
    const openBtn = document.querySelector('.openBtn');
    const closeBtn = document.querySelector('.closeBtn');

    openBtn.addEventListener('click', () => {
        dialog.showModal();
    });
    closeBtn.addEventListener('click', () => {
        dialog.close();
    });
</script>

showModal と close メソッドを利用することで Dialog の表示/非表示を切り替えれるようになりました。

form タグの利用

close メソッドを利用して Dialog を非表示にすることができましたが form 要素を利用することで Dialog を非表示にすることができます。form 要素を利用する場合には method 属性に”dialog”を設定し button 要素を追加します。form 要素は dialog 要素の中に追加します。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>dialog element</title>
  </head>
  <body>
    <button class="openBtn">Open</button>
    <dialog>
      <h2>Dialog</h2>
      <button class="closeBtn">Close</button>
      <form method="dialog">
        <button>submit</button>
      </form>
    </dialog>
    <script>
      const dialog = document.querySelector('dialog');
      const openBtn = document.querySelector('.openBtn');
      const closeBtn = document.querySelector('.closeBtn');

      openBtn.addEventListener('click', () => {
        dialog.showModal();
      });
      closeBtn.addEventListener('click', () => {
        dialog.close();
      });
    </script>
  </body>
</html>

form 要素を設定後は close メソッドを利用しなくても”submit”ボタンをクリックすることで Dialog を非表示にすることができるようになりました。method 属性の値を”dialog”から変更または削除すると動作しページの再読み込みが行われます。Dialog は非表示になりますが非表示から表示になったのではなくページの再読み込みによって元の状態に戻っただけです。

form 要素の method 属性ではなく button 要素の formmethod 属性に”dialog”を設定しても Dialog を非表示にすることができます。formmethod を利用するとデフォルトの動作であるページの再読み込みも行われず method に”post”を設定している場合でも POST リクエストが送信されません。form を実行させたいことから formmethod 属性は cancel ボタンなどに利用することができます。


<!DOCTYPE html'gt;
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>dialog element</title>
  </head>
  <body>
    <button class="openBtn">Open</button>
    <dialog>
      <h2>Dialog</h2>
      <button class="closeBtn">Close</button>
      <form method="post">
        <button formmethod="dialog">cancel</button>
        <button>submit</button>
      </form>
    </dialog>
    <script>
      const dialog = document.querySelector('dialog');
      const openBtn = document.querySelector('.openBtn');
      const closeBtn = document.querySelector('.closeBtn');

      openBtn.addEventListener('click', () => {
        dialog.showModal();
      });
      closeBtn.addEventListener('click', () => {
        dialog.close();
      });
    </script>
  </body>
</html>

“cancel”ボタンを追加

cancelボタンの追加
cancelボタンの追加

ここまでの動作確認で Dailog を非表示にする際には”Escape キー”, “close メソッド”, “form 要素”を利用して行えることがわかりました。

close イベント

Dialog を非表示にする場合に close イベントを設定することで Dialog が非表示になったことを検知することができます。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>dialog element</title>
  </head>
  <body>
    <button class="openBtn">Open</button>
    <dialog>
      <h2>Dialog</h2>
      <button class="closeBtn">Close</button>
      <form method="dialog">
        <button>cancel</button>
        <button>submit</button>
      </form>
    </dialog>
    <script>
      const dialog = document.querySelector('dialog');
      const openBtn = document.querySelector('.openBtn');
      const closeBtn = document.querySelector('.closeBtn');

      openBtn.addEventListener('click', () => {
        dialog.showModal();
      });
      closeBtn.addEventListener('click', () => {
        dialog.close();
      });
      dialog.addEventListener('close', (e) => {
        console.log('close');
      });
    </script>
  </body>
</html>

どのボタンをクリックしてもコンソールには close イベントによって”close”の文字列が表示されます。“Escape キー”を押した場合にも close イベントが発火されます。

returnValue

form 要素内の Button 要素に value を設定することでクリックしたボタンの value の値を取得することができます。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>dialog element</title>
  </head>
  <body>
    <button class="openBtn">Open</button>
    <dialog>
      <h2>Dialog</h2>
      <button class="closeBtn" value="close">Close</button>
      <form method="dialog">
        <button value="cancel">cancel</button>
        <button value="submit">submit</button>
      </form>
    </dialog>
    <script>
      const dialog = document.querySelector('dialog');
      const openBtn = document.querySelector('.openBtn');
      const closeBtn = document.querySelector('.closeBtn');

      openBtn.addEventListener('click', () => {
        dialog.showModal();
      });
      closeBtn.addEventListener('click', () => {
        dialog.close();
      });
      dialog.addEventListener('close', () => {
        console.log('close')
        console.log('returnValue:', dialog.returnValue);
      });
    </script>
  </body>
</html>

form 要素の中のボタンを押すとコンソールには設定した value が表示されます。“cancel”ボタンであれば”cancel”、“submit”ボタンであれば”submit”とコンソールに表示されます。しかし form 要素の外側にある”close”ボタンを押した場合には”空白”またはその前に form 要素の中のクリックしたボタンの value が表示されます。dialog 要素自体が returnValue を保存しているので form 要素内のボタンを押すと returnValue が更新されますが外側の”close”ボタンをクリックすると更新されないためです。


“Escape キー”を押した場合は”close”ボタンを押した時と同じ動作となります。

returnValue の値を利用することでクリックしたボタンによって異なる処理を行うことが可能となります。

Dialog の外側のクリック

Dialog の外側にある背景をクリックすることで Dialog を非表示にする場合の設定を確認していきます。モーダルウィンドウでは背景をオーバーレイと呼ぶことがありますがオーバーレイをクリックした場合に表示されている Dialog を非表示にします。

dialog 要素の中に div 要素を追加することで追加した div 要素内であればクリックしたとしても Dialog を非表示にさせないように設定を行います。style を設定することで dialog の領域一杯にクリック領域を調整しています。

<dialog style="padding: 0">
    <div style="padding: 1em">
    <h2>Dialog</h2>
    <button class="closeBtn" value="close">Close</button>
    <form>
        <button value="cancel">cancel</button>
        <button value="submit">submit</button>
    </form>
    </div>
</dialog>

Dialog の内側をクリックしても Dialog が閉じられる場合があります。その場合は各要素の margin や padding を確認してください。

dialog 要素に対して click イベントを設定してクリックした要素が dialog 要素の場合のみ close メソッドを実行します。


dialog.addEventListener('click', (event) => {
    console.log(event.target);
    if (event.target === dialog) {
        dialog.close();
    }
});

dialog 要素の外側をクリックした場合にはコンソールには下記のように dialog 要素の情報が表示されます。

dialogの外側をクリック
dialogの外側をクリック

dialog 要素の外側をクリックした場合にはコンソールには下記のように dialog 要素の情報が表示されます。

dialogの内側をクリック
dialogの内側をクリック

クリックしても正しく非表示にならない場合は上記の情報を参考に確認を行ってください。

背景色の変更

背景色を変更したい場合や Dialog の border を消したい場合には style で設定を行うだけです。dialog に border が設定されているので”border:0”でボーダーは消えます。背景は backdrop で設定されているので backdrop に背景色を設定することで指定した色に変わります。


<style>
dialog {
    border: 0;
}
dialog::backdrop {
    background-color: skyblue;
}
</style>
背景色とボーダーのスタイル変更
背景色とボーダーのスタイル変更

アニメーションの設定

表示から非表示に非表示から表示に変わる際にアニメーションを設定する方法はいくつかあると存在しますがここでは opacity を利用してアニメーション設定を行います。

説明した通り dialog 要素は非表示から表示の切り替えは display の値を”none”から”block”にすることで実現しています。非表示の状態では display を”block”で opacity は 0 にして非表示とします。


<style>
    dialog {
        border: 0;
        display: block;
    }
    dialog:not([open]) {
        opacity: 0;
    }
    dialog::backdrop {
        background-color: skyblue;
    }
</style>

設定しても動作に変わりはありませんが opacity は 0 で要素は存在はしているがブラウザ上には見えない状態となっています。display が”none”の場合は DOM に dialog 要素は存在しません。

transition を利用してアニメーションの設定を行います。


<style>
    dialog {
        border: 0;
        display: block;
        transition: opacity 0.5s ease-out;
    }
    dialog:not([open]) {
        opacity: 0;
    }
    dialog::backdrop {
        background-color: skyblue;
    }
</style>

transition を追加することで”Open”ボタンをクリックするとゆっくりと Dialog が表示されます。しかし Dialog を非表示にするためボタンか Dialog の外側をクリックすると 画面中央で非表示になるのではなく display の値を”block”にして opacity を設定していない時の位置に Dialog が一瞬表示されて消えます。

その位置は display の値を”block”にして opacity を設定していない設定を行うことで確認することができます。


<style>
    dialog {
        border: 0;
        display: block;
    }
    dialog::backdrop {
        background-color: skyblue;
    }
</style>
Dialogを表示させた場合の位置
Dialogを表示させた場合の位置

style を利用して Dialog の表示場所を設定します。position は fixed, inset は 0 にしています。


<style>
    dialog {
        border: 0;
        display: block;
        position: fixed;
        inset: 0;
        transition: opacity 0.5s ease-out;
    }
    dialog:not([open]) {
        opacity: 0;
    }
    dialog::backdrop {
        background-color: skyblue;
    }
</style>

position を設定後は Dialog が表示された場所と同じ場所の画面中央で非表示になるためゆっくりと Dialog が表示され、ゆっくりと非表示になります。

背景のスクロールの停止

ページ内のコンテンツが多くスクロールが可能なページで Dialog を表示すると Dialog が中央に表示されていても背景のスクロールを行うことができます。Dialog が表示された状態で背景のスクロールを停止するために以下の style を追加します。

index.html


html:has(dialog[open]) {
  overflow: hidden;
}

Dialog が表示されている場合は overflow の設定が有効になり背景のスクロールを止めることができます。

ここまで読み進めた人であれば dialog 要素の理解が深まり、dialog 要素を利用したモーダルウィンドウの作成も理解できたのではないでしょうか。

最終的なコードは下記の通りです。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>dialog element</title>
    <style>
      dialog {
        border: 0;
        display: block;
        position: fixed;
        inset: 0;
        transition: opacity 0.5s ease-out;
      }
      dialog:not([open]) {
        opacity: 0;
      }
      dialog::backdrop {
        background-color: skyblue;
      }
      html:has(dialog[open]) {
        overflow: hidden;
      }
    </style>
  </head>
  <body>
    <button class="openBtn">Open</button>
    <dialog style="padding: 0">
      <div style="padding: 1em">
        <h2>Dialog</h2>
        <button class="closeBtn" value="close">Close</button>
        <form>
          <button value="cancel">cancel</button>
          <button value="submit">submit</button>
        </form>
      </div>
    </dialog>
    <script>
      const dialog = document.querySelector('dialog');
      const openBtn = document.querySelector('.openBtn');
      const closeBtn = document.querySelector('.closeBtn');

      openBtn.addEventListener('click', () => {
        dialog.showModal();
      });
      closeBtn.addEventListener('click', () => {
        dialog.close();
      });
      dialog.addEventListener('close', () => {
        console.log('returnValue:', dialog.returnValue);
      });
      dialog.addEventListener('click', (event) => {
        console.log(event.target);
        if (event.target === dialog) {
          dialog.close();
        }
      });
    </script>
  </body>
</html>