5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【初学者向け】WAI-ARIAを使ってモーダルウィンドウを作る

Posted at

今まで私はjQueryでモーダルウィンドウを実装していたのですが、最近jQueryを使わないことが増えたのでJavaScriptで実装した際の備忘録です。

使用するWAI-ARIAの属性

aria-controls

指定した要素が値に指定した要素を制御することを示すWAI-ARIAの属性です。
(※HTMLリファレンスから引用)

今回の場合は、モーダルウィンドウを開閉するボタンに使用します。
対応するモーダルウィンドウのidと同じ文字列を、開閉するボタンのaria-controlsの値に設定することで、**「このボタンとこのモーダルウィンドウが対応している」**という設定をします。

aria-expanded

要素の開閉の状態を示すためのWAI-ARIAの属性です。
(※HTMLリファレンスから引用)

こちらも今回の場合はモーダルウィンドウを開閉するボタンに使用します。
対応する要素が閉じている場合はfalse、開いている場合はtrueを設定します。
例えば、モーダルウィンドウを開くボタンを押下するときは、対応するモーダルウィンドウは閉じているはずです。
そのため、モーダルを開くボタンにはaria-expanded="false"、モーダルを閉じるボタンにはaria-expanded="true"を設定します。

aria-hidden

ユーザーエージェントに認識させたくない要素に指定するためのWAI-ARIA属性です。
(※HTMLリファレンスから引用)

display:none;visibility:hidden;で隠れている要素に指定する属性です。
今回の場合、モーダルウィンドウは最初非表示になっているので、モーダルウィンドウの大枠の要素に
aria-hidden="true"を設定します。

HTMLの準備

modal.html
<!-- モーダルウィンドウ -->
<div class="p-modal" id="modal_01" aria-hidden="true">
  <div class="p-modal__wrap">
   <!-- ↓モーダルを閉じるボタン↓ -->
     <button class="js-modalClose" aria-controls="modal_01" aria-expanded="true"></button>
      <div class="p-modal__contents">
        モーダルの中身
      </div>
   </div>
</div>

モーダルウィンドウ側のHTMLには、まず大枠の要素にidを付与します。
開いたモーダルウィンドウ内に、そのモーダルウィンドウを閉じるボタンを設置する場合は、
js-modalCloseといった閉じる用のクラスを付与し、aria-controlsには閉じる対象のモーダルウィンドウに付与されているidと同じものを記述します。

index.html
<!-- モーダルを開くためのボタン -->
<button class="js-modalOpen" aria-controls="modal_01" aria-expanded="false"></button>

モーダルウィンドウを開くボタンには、js-modalOpenといったクラスを付与し、
aria-controlsには開く対象となるモーダルウィンドウに付与されているidと同じものを記述します。

CSSで整形

デザイン面は除き、モーダルウィンドウの表示非表示に関わる部分だけ記載します。

style.scss
.p-modal {
  &[aria-hidden="true"] {
    visibility: hidden;
    opacity: 0;
    transition: .4s;
  }
  &[aria-hidden="false"] {
    visibility: visible;
    opacity: 1;
    transition: .4s;
  }
}

aria-hidden属性がtrue(要するに非表示)の場合はvisibility: hidden;で隠しています。
display: none;でもいいのですが、transitionが効かなかったりするので今回はvisibilityを使用しました。
反対にaria-hidden属性がfalse(要するに表示)の場合はvisibility: visible;で表示させ、
opacitytransitionでフェードインするような表示の仕方をさせています。

実際にJavaScriptで表示非表示を制御してみる

さて、ここからが本題です。
細かい説明は、各所にコメントアウトで記載しています。

modal.js
function modalWindow() {

  // ここで、HTML上にあるモーダル開閉用のクラスを全て取得
  const modalOpenBtn = document.querySelectorAll('.js-modalOpen');
  const modalCloseBtn = document.querySelectorAll('.js-modalClose');

  // 取得したモーダルを開くボタン一個一個に対しforEachで処理
  modalOpenBtn.forEach((elm) => {
    // ボタンがクリックされたら
    elm.addEventListener('click', () => {
      // ボタンに付与されているaria-controlsの値を取得し、それと同じidが付与されているモーダルウィンドウを取得する
      let targetId = elm.getAttribute('aria-controls');
      let target = document.getElementById(targetId);

      // モーダルが非表示だった場合
      if (target.getAttribute('aria-hidden') === 'true') {
        // ボタンのaria-expanded属性を変更し、対応するモーダルが開いたと設定する
        elm.setAttribute('aria-expanded', 'true');
        // モーダルのaria-hidden属性を変更し、モーダルが開いたと設定する
        target.setAttribute('aria-hidden', 'false');
        // 現在のスクロール位置を取得した後、bodyを固定させる
        const scrollY = window.scrollY;
        document.body.style.position = 'fixed';
        document.body.style.top = -scrollY + 'px';
        document.body.style.left = '0';
        document.body.style.right = '0';
      }
    });
  });

  // 取得したモーダルを閉じるボタン一個一個に対しforEachで処理
  modalCloseBtn.forEach((elm) => {
    // ボタンがクリックされたら
    elm.addEventListener('click', () => {
      // ボタンに付与されているaria-controlsの値を取得し、それと同じidが付与されているモーダルウィンドウを取得する
      let targetId = elm.getAttribute('aria-controls');
      let target = document.getElementById(targetId);

      // モーダルが表示されている場合
      if (target.getAttribute('aria-hidden') === 'false') {
        // 開くボタンクリック時に設定されたbodyのtopを取得し、bodyの固定を解除
        const scrollY = document.body.style.top;
        document.body.style.position = '';
        document.body.style.top = '';
        document.body.style.left = '';
        document.body.style.right = '';
        // スクロール位置をモーダルを開いた時と同じ位置に戻す
        window.scrollTo(0, parseInt(scrollY || '0') * -1);
        // ボタンのaria-expanded属性を変更し、対応するモーダルが閉じたと設定する
        elm.setAttribute('aria-expanded', 'false');
        // モーダルのaria-hidden属性を変更し、モーダルが閉じたと設定する
        target.setAttribute('aria-hidden', 'true');
      }
    });
  });
};

modalWindow();

固定する背景に色を付けたいとか、固定背景クリックでモーダルを閉じるようにしたい場合もあると思いますが、それに関してはまたの機会に…

さいごに

こういった構成にすることで、モーダルが何個に増えても簡単に対応でき、後の作業が楽になったかなと思います。
慣れたらもっとスマートなコードが書けるようになると思うので、その際はまた加筆修正行いたいと思います。

読んでいただきありがとうございました!

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?