dialog要素を使う今後のモーダル実装
こんにちハ! こんばんハ!
フロントエンドエンジニアとして働いているあんころもちたろうです。
初めて記事を書くので緊張気味です・・・
対象ブラウザにIE11が含まれない時代へ・・・
そろそろIE11対象外のweb制作が増えてくるのかなと思ってます。
遅れを取らないようにCSS・JSの情報をキャッチアップをしていましたが、HTMLを疎かにしておりました
というわけで!
そろそろ業務でも使われるdialog要素を実際に使ってモーダルを実装してみましたので、共有させていただきます。
HTMLの記述
まずはdialog要素をどのように記述するか確認します。
<dialog class="dialog">
  <div class="dialog_inner">
    <p>dialog内のテキスト</p>
    <button class="btn close" type="button">閉じる</button>
  </div><!-- /.dialog_inner -->
</dialog>
モーダルとして表示させるものをdialog要素内に記述します。
これだけ記述してもブラウザには表示されません。
実装
まず、モーダルを開くためのボタンを用意します。
<dialog class="dialog" open>
  <div class="dialog_inner">
    <p>dialog内のテキスト</p>
    <button class="btn close" type="button">閉じる</button>
  </div><!-- /.dialog_inner -->
</dialog>
<button class="btn open" type="button">モーダル</button>
.btn.openをクリックしたらモーダル(dialog要素)を見えるようにします。
      const modal = document.querySelector(".dialog");
      const openTrigger = document.querySelector(".btn.open");
      openTrigger.addEventListener("click", () => {
        modal.showModal();
      });
モーダルを開くときは、showModal()メソッドを使います。
showModal()メソッドを使ってモーダルを開くとdialog要素に::backdropという疑似要素がつき、これがオーバーレイになります!
今までクラスの付け替えやスタイルの変更をしていた箇所がこんなにわかりやすくなるとは。。
オーバーレイにスタイルを当てたいときは、CSSでスタイルを当てることができます!
      dialog::backdrop {
        background: rgba(94, 94, 94, 0.5);
        backdrop-filter: blur(4px);
      }
今度は.btn.closeをクリックしたらモーダルを閉じられるようにします。
      const modal = document.querySelector(".dialog");
      const openTrigger = document.querySelector(".btn.open");
      const closeTrigger = document.querySelector(".btn.close"); // <-追加
      openTrigger.addEventListener("click", () => {
        modal.showModal();
      });
      closeTrigger.addEventListener("click", () => { // <-追加
        modal.close();
      });
モーダルを閉じるときは、close()メソッドを使います。
ここまでで簡単なモーダルを実装できました。
dialog要素を使うと、
モーダルを開いた状態でescキーを押すとモーダルが閉じてくれるので、
今までJSでやっていたものが不要になります
さらに、モーダルを開いているとき、tabキーでオーバーレイより下の要素にフォーカスが当たらないので、面倒だったJSの処理も不要になります!
しかし、このままだとモーダル開いたときに、オーバーレイより下がスクロールしてしまったり、
オーバーレイをクリックしてもモーダルが閉じないので、それらを実装してみます。
スクロールしないように
CSSでbody要素にoverflow: hidden;を当てるためのクラスを用意します。
      dialog::backdrop {
        background: rgba(94, 94, 94, 0.5);
        backdrop-filter: blur(4px);
      }
      body.is-modal { /* 追加 */
        overflow: hidden;
      }
JSでモーダルを開いているときは、body要素に.is-modalが付与され、
閉じたときは、body要素から.is-modalを削除するようにします。
      const modal = document.querySelector(".dialog");
      const openTrigger = document.querySelector(".btn.open");
      const closeTrigger = document.querySelector(".btn.close");
      const { body } = document; // <-追加
      const MODAL_CLASS = "is-modal"; // <-追加
      openTrigger.addEventListener("click", () => {
        body.classList.add(MODAL_CLASS); // <-追加
        modal.showModal();
      });
      closeTrigger.addEventListener("click", () => {
        body.classList.remove(MODAL_CLASS); // <-追加
        modal.close();
      });
このままだと、escキーでモーダルを閉じたときにbody要素から.is-modalが削除されないので、
escキーを押したときの処理を追加します。
      const modal = document.querySelector(".dialog");
      const openTrigger = document.querySelector(".btn.open");
      const closeTrigger = document.querySelector(".btn.close");
      const { body } = document;
      const MODAL_CLASS = "is-modal";
      openTrigger.addEventListener("click", () => {
        body.classList.add(MODAL_CLASS);
        modal.showModal();
      });
      closeTrigger.addEventListener("click", () => {
        body.classList.remove(MODAL_CLASS);
        modal.close();
      });
      modal.addEventListener("cancel", () => { // <-追加
        body.classList.remove(MODAL_CLASS);
      });
cancelイベントを使うことで対応できます!
オーバーレイをクリックしたら閉じるように
モーダルを開いているときに、クリックした要素を取得してオーバーレイ箇所かどうか判定します。
まずCSSを準備します。
      dialog::backdrop {
        background: rgba(94, 94, 94, 0.5);
        backdrop-filter: blur(4px);
      }
      body.is-modal {
        overflow: hidden;
      }
      dialog { /* 追加 */
        padding: 0;
      }
      .dialog_inner { /* 追加 */
        padding: 16px;
      }
JSでモーダルを開いているときに、クリックした要素を判定して、親要素に.dialog_innerがなければ閉じるようにします。
      const modal = document.querySelector(".dialog");
      const openTrigger = document.querySelector(".btn.open");
      const closeTrigger = document.querySelector(".btn.close");
      const { body } = document;
      const MODAL_CLASS = "is-modal";
      openTrigger.addEventListener("click", () => {
        body.classList.add(MODAL_CLASS);
        modal.showModal();
      });
      closeTrigger.addEventListener("click", () => {
        body.classList.remove(MODAL_CLASS);
        modal.close();
      });
      modal.addEventListener("cancel", () => {
        body.classList.remove(MODAL_CLASS);
      });
      modal.addEventListener("click", (e) => { // <-追加
        if (!e.target.closest(".dialog_inner")) {
          body.classList.remove(MODAL_CLASS);
          modal.close();
        }
      });
補足
open属性
dialog要素にはopen属性があります。
<dialog class="dialog" open>
  <div class="dialog_inner">
    <p>dialog内のテキスト</p>
    <button class="btn close" type="button">閉じる</button>
  </div><!-- /.dialog_inner -->
</dialog>
open属性を付与するとモーダルが表示されますが、::backdrop(オーバーレイ)がない状態で表示されます。
 show()メソッド
modal.showModal();ではなく、modal.show();でもモーダルを開くことはできますが、
::backdrop(オーバーレイ)がない状態で表示されます。
 openプロパティ
modal.openでモーダルが開いているか閉じているか判定することができます。
modal.openの戻り値はboolean。
まとめ
今までJSでやっていたことが、不要になるってスバラシイです!
dialog要素を使った実装についてまとめてみます。
dialog要素の良いところ
- tabキーでモーダル開いた後に、tabキーを押すとオーバーレイより下のレイヤーにフォーカスが当たらないので便利!(今までJSでやっていたものが不要に)
 - 今までモーダルを開いたり閉じたりする実装は、クラスの付け替えで実装していたが、以下だけでモーダルを開いたり閉じたりできるのが直感的で便利!
modal.close();modal.showModal();modal.show();
 - 
showMolda()で開いた時に疑似要素のbackdropがつく。CSSのdialog::backdropでスタイリング可能! - 
modal.openでモーダルが開いているか閉じているか判定ができる - モーダル開いた状態でESCキーを押すとモーダルを閉じることができる!(今までJSでやっていたものが不要に)
 - ESCキーで閉じた時のイベントがある!
cancelイベント 
惜しいところ
- モーダルより下がスクロールしてしまうので、結局bodyのクラス付け替えが必要。。。
 - オーバーレイ箇所をクリックした時の判定がないため、自作しなければならない。。。
 
Discussion