今まで私は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の準備
<!-- モーダルウィンドウ -->
<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と同じものを記述します。
<!-- モーダルを開くためのボタン -->
<button class="js-modalOpen" aria-controls="modal_01" aria-expanded="false"></button>
モーダルウィンドウを開くボタンには、js-modalOpen
といったクラスを付与し、
aria-controls
には開く対象となるモーダルウィンドウに付与されているidと同じものを記述します。
CSSで整形
デザイン面は除き、モーダルウィンドウの表示非表示に関わる部分だけ記載します。
.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で表示非表示を制御してみる
さて、ここからが本題です。
細かい説明は、各所にコメントアウトで記載しています。
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();
固定する背景に色を付けたいとか、固定背景クリックでモーダルを閉じるようにしたい場合もあると思いますが、それに関してはまたの機会に…
さいごに
こういった構成にすることで、モーダルが何個に増えても簡単に対応でき、後の作業が楽になったかなと思います。
慣れたらもっとスマートなコードが書けるようになると思うので、その際はまた加筆修正行いたいと思います。
読んでいただきありがとうございました!