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

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

## 使用するWAI-ARIAの属性
### aria-controls
>指定した要素が値に指定した要素を制御することを示すWAI-ARIAの属性です。
(※[HTMLリファレンス](http://www.webcreativepark.net/html/)から引用)

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

### aria-expanded
>要素の開閉の状態を示すためのWAI-ARIAの属性です。
(※[HTMLリファレンス](http://www.webcreativepark.net/html/)から引用)

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

### aria-hidden
>ユーザーエージェントに認識させたくない要素に指定するためのWAI-ARIA属性です。
(※[HTMLリファレンス](http://www.webcreativepark.net/html/)から引用)

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

## HTMLの準備

```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と同じものを記述します。

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

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

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

```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;`で表示させ、
`opacity`と`transition`でフェードインするような表示の仕方をさせています。

## 実際にJavaScriptで表示非表示を制御してみる
さて、ここからが本題です。
細かい説明は、各所にコメントアウトで記載しています。

```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();
```
固定する背景に色を付けたいとか、固定背景クリックでモーダルを閉じるようにしたい場合もあると思いますが、それに関してはまたの機会に…

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

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