2018.12.25

入門書の次にチャレンジ!JavaScript実践クイズ〜モーダルウィンドウ編〜


この記事では、入門書などを読んで JavaScript の文法をひととおり知ったあとに、より実践的に JavaScript の活用法を学べるクイズを紹介します。応用が効くように、実際の開発で「ありがち」な実装パターンを扱っていきたいと思います。

問題

モーダルウィンドウを作ってみよう

今回の問題ではモーダルウィンドウを実装します。

See the Pen REVmbY by Masahiro Harada (@MasahiroHarada) on CodePen.

後述する仕様の通りに動作するように JavaScript を編集しましょう。
穴埋めになっているので /* Insert code here... */ の箇所にコードを足してください。

HTML と CSS は出来ているので変更しません。
ただし重要なヒントですのでよく確認してください。

手元にソースコードを落としてきたい場合は GitHubGist からどうぞ 🐙

前提条件

  • jQuery などのライブラリは使用せずに実装してください。
  • 対応ブラウザは Google Chrome 最新版とします。
  • 解答例では ES2015 以降の文法も使用します。

モーダル機能の仕様

開く

data-modal-open 属性を付与した要素をクリックすると、data-modal-open の属性値を id に持つ要素をモーダルウィンドウとして表示します。

<button data-modal-open="myModal">Open Modal</button>

<div id="myModal"><!-- Content --></div>

閉じる

モーダル内の data-modal-close 属性を持つ要素をクリックすると、その要素を内包するモーダルウィンドウが閉じられます。

<div id="myModal">
  <button data-modal-close>Close Modal</button>
</div>

🤔

🤔

🤔

🤔

🤔

回答例

document.querySelectorAll('[data-modal-open]').forEach(elem => {
  // 開閉する対象の要素を取得する
  const targetModalId = elem.getAttribute('data-modal-open');
  const modal = document.getElementById(targetModalId);

  // 閉じる
  modal.querySelectorAll('[data-modal-close]').forEach(closeElem => {
    closeElem.addEventListener('click', function() {
      modal.classList.remove('is-open');
    });
  });

  // 開く
  elem.addEventListener('click', function() {
    modal.classList.add('is-open');
  });
});

解説

今回の大きなポイントはクラスの追加と削除です。

モーダルをどうやって開閉させるかを考えるにあたって、まずは CSS を確認します。開いているか閉じているかは見た目の問題なので CSS に書かれているのでは?と予想できれば 💯 ですね。

.modal {
  display: none;
}

.modal.is-open {
  display: block;
}

上記から、is-open というクラスが付与されているモーダルは表示されて(つまり開いた状態)、付いていなければ表示されない(閉じた状態)ことが読み取れます。

ということは、開くボタンをクリックしたときに対象のモーダルのクラス属性に is-open を追加して、閉じるボタンをクリックしたときに is-open クラスを削除すればよい、というふうに実装手順が想像できます。

ここから問題文の JavaScript を見ていきます。

document.querySelectorAll('[data-modal-open]').forEach(elem => {
  // elem はひとつの開くボタン
});

まず querySelectorAll メソッドで data-modal-open 属性を持つ要素のリスト(つまり開くボタン)を取得し、forEach メソッドでループしています。

forEach メソッドの引数である関数は、ひとつひとつの開くボタンを引数にとって、開くボタンの数だけ実行されるので、ここで開くボタンに対応するモーダル要素を特定できます。

const targetModalId = elem.getAttribute('data-modal-open');
const modal = document.getElementById(targetModalId);

冒頭の仕様説明の通り、getAttribute メソッドで data-modal-open 属性値を取得し、その値(targetModalId)を id に持つ要素を getElementById メソッドで取得します。

モーダルの要素を特定できたので、最初に検討をつけた開閉の実装手順の通り、is-open クラスの追加と削除を実装します。クラス属性の追加や削除を実現するには、HTML 要素の classList プロパティ に定義されたメソッドを使用します。

element.classList.add('class-name');
element.classList.remove('class-name');

以上を踏まえて、閉じる機能を完成させます。

modal.querySelectorAll('[data-modal-close]').forEach(closeElem => {
  closeElem.addEventListener('click', function() {
    modal.classList.remove('is-open');
  });
});

閉じるボタンがモーダル内に複数ある可能性を考慮して querySelectorAll メソッドから forEach メソッドを繋げてループ処理を記述しています。

querySelectorAll の呼び出し元が document ではなく modal であることに注目してください。document から呼び出した場合は HTML 文書中のすべての要素からセレクタに合致する要素が検索されますが、特定の要素から querySelectorAll を呼び出した場合は、その要素が内包する子要素群からセレクタに合致する要素が検索されます。

closeElem はひとつの閉じるボタン要素なので、この要素のクリックイベントでモーダル要素から is-open クラスを取り除きます。

開く機能は以下の通りです。

elem.addEventListener('click', function() {
  modal.classList.add('is-open');
});

ここまでくるともう簡単ですね。
開くボタンのクリックイベントでクラスを追加しています。


以上、モーダルウィンドウの実装クイズでした。
JavaScript クイズはいくつかシリーズ的に掲載する予定です。お楽しみに 😊