簡易的なモーダルウィンドウをライブラリを使用せずに実装する方法を紹介します。
最初は簡単なモーダルの実装から、最終的にアクセシビリティを考慮してキーボードだけで操作できるモーダルまで段階的に紹介していきます。
必要なモーダルウインドウのコードを利用してもらえればと思います。
目次
最も簡単なモーダルウィンドウ
まず最初はとりあえずモーダルウインドウが実装できれば問題なし!という人のための簡単なモーダルウインドウです。
htmlやcssをそのまま利用したい人は「View Compiled」をクリックして利用してください。
See the Pen simple window1 by takblog (@blanks-site) on CodePen.
Javascriptは難しいことはしていなくて、
<div class="modal__open">をクリックしたら、<div class="modal">にclass「is-open」を追加する。
<div class="modal__close">をクリックしたら、<div class="modal">のclass「is-open」を削除する。
といったことしかしていません。
ポイントはcssの「visibility」と「transition」です。
最初<div class="modal">はopacity:0;で、コンテンツ(モーダルオープンボタンしかありませんが)は閲覧できますが、このままではコンテンツにある要素全く触れない状態です。
そこでvisibility:hidden;を使うことでコンテンツにある要素のクリックなどが可能になります。
最初はdisplay:none;で<div class="modal is-open">のときはdisplay:block;で表示でも問題ないですが、オープン・クローズ時にcssアニメーションをつけたい場合には不向きなので、visibilityを使用しています。
次のポイントのtransitionです。cssをよく見てもらうと、同じようなtransitionが2ヶ所書いてありますが、間違いではありません。
「.modal.is-open」の時はvisibilityのtransition-delayが0sに対し、「.modal」の時はtransition-delayが0.3sです。
「.modal.is-open」はモーダルオープン時のtransitionです。モーダルオープン時は先にvisibilityをvisibleにしておかないと、0.3秒かけてopacityが0->1になってもモーダルは表示されません。
逆に「.modal」はモーダルクローズ時のtransitionです。モーダルクローズ時は先にvisibilityがhiddenになってしまうと、すぐにモーダルが非表示になってしまいます。そこでvisibilityのtransition-delayを0.3sにして、opacityが徐々に0になった後にvisibilityがhiddenになるようにしています。
このモーダルが使えるとき
- 1ページに1つしかモーダルを用意しない
- モーダルオープンボタンやモーダルクローズボタンを1つずつしか設置しない
- cssアニメーションでオープン・クローズのアニメーションを設定できる
モーダルの問題点
- モーダルオープン時、コンテンツエリアがスクロールできてしまう
- クローズボタンクリックでしか、クローズしない(背景をクリックしてもクローズしない)
- 複数のモーダルや複数のモーダルオープンボタンを用意すると、意図しない動きになる
- アクセシビリティが考慮されていない
以上の問題点を1つ1つクリアしていきたいと思います。
モーダルオープン時にコンテンツエリアをスクロールさせない
ここでは次の2つの問題点を解決していきます。
- モーダルオープン時、コンテンツエリアがスクロールできてしまう
- クローズボタンクリックでしか、クローズしない(背景をクリックしてもクローズしない)
まず最初はモーダルオープン時にコンテンツエリアをスクロールさせないようにし、背景をクリックしてもクローズするようにします。
背景クリックでクローズするようにするには、モーダルクローズの処理をそのまま流用します。
コンテンツエリアをスクロールさせないのは簡単でbodyや全体を囲んでいる要素にposition:fixed;を付けてあげれば解決します。
しかし単純にfixedを付けるだけだと、モーダルクローズ時にページトップに戻ってしまうので、それを防ぐためにJavascriptで色々と処理してあげます。
実装したのは以下のものです。
See the Pen simple window2 by takblog (@blanks-site) on CodePen.
最初のモーダルと違って、モーダルオープン時に要素全体のスクロールバーの表示がなくなり、要素全体はスクロールできなくなっています。
全体を<div class="wrapper">で囲んでいるので、モーダルオープン時にはwrapperにclass「is-modalopen」が付くようにしています。
またモーダルオープン時のJavascriptの処理はmodalOpen関数、モーダルクローズ時のJavascriptの処理はmodalClose関数にまとめているので、それぞれ簡単に説明していきます。
modalOpen関数
次の説明は「コードをポップアップで見る」で開いて読んでください。
2行目でページのスクロール量を取得しています。
3行目でclass「is-modalopen」をwrapperに追加していますが、2行目と3行目が逆になると正常に動きません。
is-modalopenはposition : fixed ;のstyleを適用するので、この状態でwindow.pageYOffsetを取得すると0になるので、気をつけましょう。
4行目で取得したスクロール量のマイナスの値をwrapperのtopの値として適用しています。
このstyleを適用しないと、モーダルオープン時にコンテンツエリアはページトップまで戻ります。
modalClose関数
次の説明は「コードをポップアップで見る」で開いて読んでください。
modalClose関数ではcallback関数を使用しています。
モーダルクローズ時にwrapperからclass「is-modalopen」を削除した後、モーダルオープン前までにスクロールしていた位置までスクロールさせるという処理を行いますが、callbackを使用しないと元の位置までスクロールされないということが度々起きないので、callback関数を使用しています。
4行目ではwrapperに適用していたtopの値を取得しています。
このとき取得できる値は「-〇〇px」になるので数字の部分だけ残すために、5行目で top.slice(1,-2)の処理をして、yには「〇〇」が入るようにしています。
ここで取得できた値yをcallbackで返しています。
10〜12行目は取得できたyの値のとこまでページ全体をスクロールさせています。
ここまでで問題点の2つを解決しました。
モーダルオープン時、コンテンツエリアがスクロールできてしまうクローズボタンクリックでしか、クローズしない(背景をクリックしてもクローズしない)- 複数のモーダルや複数のモーダルオープンボタンを用意すると、意図しない動きになる
- アクセシビリティが考慮されていない
モーダルを複数用意してもOKにする
次に以下の問題点を解決していきます。
- 複数のモーダルや複数のモーダルオープンボタンを用意すると、意図しない動きになる
複数設置するにあたって考えなくていけないのは、オープンボタンがどのモーダルに対応しているかを判定するための処理をどう行うかです。方法はclassやidを使うなど色々あると思いますが、ここではdata属性を使って対応するモーダルを判定していきます。
また最初にあげた問題点とは別ですが、モーダルを複数設置となるとモーダルのコンテンツ要素を忘れることはないですが、モーダルの背景となる<i class="modal__bg"></i>を書き忘れることはありそうです。
そこで上記とプラスして下記の対応もしていきます。
- モーダル背景要素をJavascript側の処理で設置する
実装したのは次のものです。
See the Pen simple window3 by takblog (@blanks-site) on CodePen.
htmlは先程までとは違って、モーダルオープンボタンやモーダル要素に「data-modal」属性が追加されています。このdata-modal属性によって、どのボタンがどのモーダルに対応しているかを判定しています。
Javascriptのコードが先程までとはだいぶ異なってくるので、異なる部分を1つずつ説明していきます。
2行目、3行目は先程までは「querySelector」で取得していたオープンボタン、クローズボタンですが「querySelectorAll」で取得しており、clickイベントでの処理もforEachで回しています(37〜48行目)。
これでオープンボタン、クローズボタンが複数設置されても問題ないようにしています。
またオープン時にはmodalOpen関数にクリックしたボタン要素を渡すようにしています。
また先程まではmodal要素やmodalbg要素を最初に定義していましたが、ここでは最初に定義はしていません。
modalOpen関数(6〜18行目)
7行目、受け取ったボタン要素からdata-modalの値を取得しています。
8行目、受け取ったdata-modalの値からオープンするモーダル要素を取得しています。
9行目、モーダル要素にモーダルの背景となる「<i class="modal__bg"></i>」を挿入しています。
14〜16行目、9行目で挿入したモーダル背景を取得し、モーダル背景をクリックしてもクローズするような処理を追加しています。モーダル背景である「document.querySelector('.modal__bg')」は9行目で挿入した後でしか取得できないので書く順番を気をつけます。
modalClose関数(20〜35行目)
21行目、オープンしているモーダル要素を取得しています。
24行目、モーダル背景を取得しています。モーダル背景要素はmodalOpen関数で追加された要素で、最初から定義はできないので、ここで定義しています。
29行目、モーダル背景要素を削除しています。ここで削除しないと、同じモーダルをオープンする度に背景要素が2つ、3つ、、、と増えていきます。
ここまでで3つの問題点をクリア+モーダル背景要素の自動追加を行うことができました。
モーダルオープン時、コンテンツエリアがスクロールできてしまうクローズボタンクリックでしか、クローズしない(背景をクリックしてもクローズしない)複数のモーダルや複数のモーダルオープンボタンを用意すると、意図しない動きになる- アクセシビリティが考慮されていない
モーダルオープン時に、別のモーダルをオープンさせる
次は最初にあげた問題点とは別ですが、ギャラリーっぽくするためにモーダルをオープンしている時に、別のモーダルをオープンできるようにしてみたいと思います。
先程のJavascriptの修正で複数のオープンボタンでモーダルをできるようにしたので、モーダルのコンテンツエリアの中に、別のモーダルをオープンするためのボタンを設置すると、別のモーダルをオープンできるようになります。
しかし先程までの状態では先にオープンしていたモーダルをクローズするという処理がないため、2つのモーダルがオープンした状態になってしまいます。
そこでmodalOpen関数とmodalClose関数を少しだけ修正します。
実装したのは次のものです。
See the Pen simple window4 by takblog (@blanks-site) on CodePen.
modalOpen関数
2行目、modalOpen関数の最初にmodalClose関数を呼び出すようにします。
modalOpen関数の調整はこれだけです。
modalClose関数
3行目、modalがnullだった場合はreturn falseを返して、ここで処理を終わらせています。
modalClose関数の調整もこれだけです。
以上の処理を追加するだけで、モーダルオープン時に、別のモーダルをオープンさせることができます。
modalOpenさせようとする時に、最初にmodalClose関数を呼び出すことでオープンしているモーダルをクローズできます。
もちろん最初にモーダルが開いていない場合もあるので、そのときはmodalClose関数は途中で終わるようにしています。
アクセシビリティを考慮したモーダルウィンドウ
今回アクセシビリティを考慮したモーダルウィンドウを作成する上で参考にしたのはBootstrap(v4.2)のModalになります。
以下のことに考慮したモーダルになります。
- モーダル要素「aria-hidden="true"」追加
- モーダル要素「tabindex="-1"」追加
- モーダル要素「aria-labelledby」追加
- モーダル要素「aria-describedby」追加
- モーダルオープン時、モーダル要素「aria-hidden="true"」削除
- モーダルオープン時、モーダル要素「tabindex="0"」に変更
- モーダルオープン時、モーダル要素「role="dialog"」追加
- モーダルオープン時、モーダル要素「aria-modal="true"」追加
- モーダルオープン時、モーダル要素にフォーカスを移す
- モーダルクローズ時、モーダル要素「aria-hidden="true"」追加
- モーダルクローズ時、モーダル要素「tabindex="-1"」に変更
- モーダルクローズ時、モーダル要素「role="dialog"」削除
- モーダルクローズ時、モーダル要素「aria-modal="true"」削除
See the Pen simple window4 by takblog (@blanks-site) on CodePen.
Javascriptコード内に「アクセシビリティ考慮」とコメントアウトして記述されている箇所が今回行った修正になります。
tabindexの変更とフォーカス移動だけでも追記すればキーボードだけでも操作が容易なモーダルになります。
最初は簡単なモーダルの実装から徐々に色々なことができるモーダルへの実装へと段階的に説明していきました。実装する環境によって使い分けてもらったいいなと思います。