見出し画像

JavaScriptによるモーダルウィンドウの実装(プラグインなし)

業務でサイトを納品する場合、自分たちのチームで全て自由に記述できるということはなく。何かしら会社全体のサイトのテンプレートがあるものだ。

特にjQueryのVer依存したプラグインを使っていると、テンプレートが変わると急に動かなくなることがある。シンプルに実装できるものはできるだけプラグインを使わないで実装できる方が何かと便利だ。

例えば、モーダルウィンドウ(或いはポップアップと呼ぶ人もいる)も、LightBoxなどJSプラグインを使わずに(少し機能制限を許容すれば)とてもシンプルに記述できる。

様々なサイトで多用な手法が紹介されているが、今回は、ある特定のボタンであるウィンドウ、別のボタンではまた別のウィンドウと、固有の一対一対応を柔軟に実現できる関数をJavaScriptで実装してみる。

モーダルウィンドウの基本的な構造

特定のボタンのクリックをトリガーにして、モーダルウィンドウの表示/非表示を切り替える。visibilityがhiddenの場合とvvisibleの場合の2種類のクラス属性を用意し、モーダルウィンドウにその属性を追加したり削除すれば良い。

画像1

その際、z-indexを使って、全画面の半透明の黒いレクタングルを浮かせて表示させ、更にその上に実際のモーダルウィンドウを表示させるという2段構えにすることで、よく見る半透明背景化が可能になる。

また、スクロールで背景が動くのが気になる人もいるが、余り難しいことは考えずアンカー機能を使って元の位置に戻れるようにしておけばユーザビリティ上は許容範囲だと考えた。

この構造はこれらのサイトを参考に理解した。

表示/非表示のスタイル

visibilityのhiddenとvisibleだけを持つクラス属性を作る。Javascript側で実装してしまっても良いが、再利用の可能性も想定するとクラス属性にするのが良いだろう。

.ly-nonVisible {
   visibility: hidden;
}

.ly-visible {
   visibility: visible;
}

半透明のレクタングル

半透明で全体を覆うようにするにはpositionをfixedにし、幅・高さを100%にすれば良い

.ly-opBg {
   position: fixed;
   z-index: 10;
   top: 0;
   left: 0;
   width: 100%;
   height: 100%;
   background-color: #000;
   opacity: 0.4;
}

モーダルウィンドウ本体

こちらは中央に表示させるためにpositionをabsoluteにし、トランフォームで軸を移動して50%を指定する。

.ly-modalNormal {
   position: absolute;
   right: 50%;
   bottom: 50%;
   transform: translate(50%, 50%);
   -webkit-transform: translate(50%, 50%);
   -ms-transform: translate(50%, 50%);
   z-index: 20;
   margin: 0 auto;
   width: 70%;
   height: 20rem;
   padding: 3rem;
   background-color: #fff;
   font-size: 1rem;
}

中央寄せには幾つか流儀があるようだが、このサイトを参考にした。

8/15 追記

IEで変なスクロールバーが出ていることに気が付いた。変換は逆にした方が良さそうだ。

また、今回はテキストが短いので便宜的にheightを指定しているが、縦長になるようであれば指定は不要だし、横幅も固定ではなくmin-widthという手もある。

なお、モーダルウィンドウの設定自体にvisibility hiddenは記述しない。記述しても問題はないのだが、表示・非表示は単独のクラス属性にしている方が、後で引き継ぐ人には理解がしやすい。冗長に見えても別クラスにする。

閉じるボタン

閉じるボタンは、親要素の右端に表示したい。「×」印の級数を大きくすることで「閉じるボタン」に見える。

.sl-closeModalButton {
   position: absolute;
   right: 1em;
   top: 0.5em;
   font-size: 2em;
}

HTML側のモーダルウィンドウの記述

「半透明のレクタングル」+「モーダルウィンドウのセクション」を並べ、「モーダルウィンドウのセクション」の中に「閉じるボタン」を入れるようにすると、白背景のモーダルウィンドウの右端に「閉じるボタン」を設置できる。

<div id="opBg1" class="ly-opBg ly-nonVisible" onclick="modalClose('point1','opBg1','section_01')"></div>
<section id="point1" class="ly-modalNormal ly-nonVisible">
    <label class="sl-closeModalButton" onclick="modalClose('point1','opBg1','section_01')">×</label>
    <h2>ポイント1</h2>
    <p>ほげほげほげほげほげほげ</p>
</section>

4つのボタンと一対一対応させるために、「半透明のレクタングル」「モーダルウィンドウのセクション」ともに、固有のidを振っている。(上記のコードの数値が1~4と変えて並べている)

モーダルウィンドウを開くJavaScriptの関数

モーダルウィンドウを開くとは、「クラス属性をhiddenからvisibleに切り替えること」なので、classListで非表示クラスをremoveし、表示クラスをaddする。これを「半透明のレクタングル」「モーダルウィンドウのセクション」両方が表示されるようにする。

また、アンカー(location.href)を利用して、モーダルウィンドウのセクションにカーソルが飛ぶようにしておくと親切だろう。

function modalVisible(point_id,opBg_id) {
   var x = document.getElementById(point_id);
   x.classList.remove('ly-nonVisible');
   x.classList.add('ly-visible');
   var y = document.getElementById(opBg_id);
   y.classList.remove('ly-nonVisible');
   y.classList.add('ly-visible');
   window.location.href='#'+point_id;
}

固有のidは必ず引数にし、スタイルはJavaScript内で指定せずクラス属性のみの動作とする。

固有のidを関数内に入れてしまうと、同じ挙動を別のところで使うために何度も関数を作る必要がある。だが、引数であれば、一度関数を作ることで今回のような4箇所別々のモーダルウィンドウでも再利用可能だ。

また、スタイルの指定をJavaScript側でやるようにやると後で混乱しがちなので、クラス属性の指定のみとし、クラスのスタイルはCSSだけを見れば分かるようにしておく。

モーダルウィンドウを閉じるJavaScriptの関数

考え方は同じなので詳細は割愛する。なお、戻るためのアンカーの場所が別なので、その分、引数の数が多い。

function modalClose(point_id,opBg_id,anchor_id) {
   var x = document.getElementById(point_id);
   x.classList.remove('ly-visible');
   x.classList.add('ly-nonVisible');
   var y = document.getElementById(opBg_id);
   y.classList.remove('ly-visible');
   y.classList.add('ly-nonVisible');
   window.location.href='#'+anchor_id;
}

HTML側での関数の設置

ボタンに対して「モーダルウィンドウを開く関数」をonclickで呼び出す。この際、どのモーダルウィンドウを開きたいのか、idを使って引数を与える。

<section id="section_01">
   <h2>セクションタイトル</h2>
   <div>
       <button class="sl-popNormal" onclick="modalVisible('point1','opBg1')">ボタン1</button>
       <button class="sl-popNormal" onclick="modalVisible('point2','opBg2')">ボタン2</button>
       <button class="sl-popNormal" onclick="modalVisible('point3','opBg3')">ボタン3</button>
       <button class="sl-popNormal" onclick="modalVisible('point4','opBg4')">ボタン4</button>
   </div>
</section>

閉じる場合の関数も同様。

なお、「閉じるボタン」だけでなく「半透明のレクタングル」もクリックするとモーダルウィンドウを閉じるようにしておく。一般的にはモーダルウィンドウ以外のところをクリックするとモーダルウィンドウが閉じる、という認知が形成されているからだ。

<div id="opBg1" class="ly-opBg ly-nonVisible" onclick="modalClose('point1','opBg1','section_01')"></div>
<section id="point1" class="ly-modalNormal ly-nonVisible">
    <label class="sl-closeModalButton" onclick="modalClose('point1','opBg1','section_01')">×</label>
    <h2>ポイント1</h2>
    <p>ほげほげほげほげほげほげ</p>
</section>

因みに、「半透明のレクタングル」の方は、同じものが違うidで4つも存在する。これ自体は冗長なコードだが、「モーダルウィンドウを閉じる関数」を汎用的にする為にこのような記述になっている。閉じる関数に対して常に固有のidで「point1~4」を与える必要があるためだ。

以上で、シンプルなJavaScriptによってモーダルウィンドウが実装できた。

コード全体

HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JavaScriptによるモーダルウィンドウの実装</title>
<link rel="stylesheet" href="/common/css/common.css">
<script src="common/js/modalWindow.js"></script>

</head>
<body class="ly-center">
<h1>JavaScriptによるモーダルウィンドウの実装</h1>
<section id="section_01">
   <h2>セクションタイトル</h2>
   <div>
       <button class="sl-popNormal" onclick="modalVisible('point1','opBg1')">ボタン1</button>
       <button class="sl-popNormal" onclick="modalVisible('point2','opBg2')">ボタン2</button>
       <button class="sl-popNormal" onclick="modalVisible('point3','opBg3')">ボタン3</button>
       <button class="sl-popNormal" onclick="modalVisible('point4','opBg4')">ボタン4</button>
   </div>
</section>
<div>
   <div id="opBg1" class="ly-opBg ly-nonVisible" onclick="modalClose('point1','opBg1','section_01')"></div>
   <section id="point1" class="ly-modalNormal ly-nonVisible">
       <label class="sl-closeModalButton" onclick="modalClose('point1','opBg1','section_01')">×</label>
       <h2>ポイント1</h2>
       <p>ほげほげほげほげほげほげ</p>
   </section>
   <div id="opBg2" class="ly-opBg ly-nonVisible" onclick="modalClose('point2','opBg2','section_01')"></div>
   <section id="point2" class="ly-modalNormal ly-nonVisible">
       <label class="sl-closeModalButton" onclick="modalClose('point2','opBg2','section_01')">×</label>
       <h2>ポイント2</h2>
       <p>ほげほげほげほげほげほげ</p>
   </section>
   <div id="opBg3" class="ly-opBg ly-nonVisible" onclick="modalClose('point3','opBg3','section_01')"></div>
   <section id="point3" class="ly-modalNormal ly-nonVisible">
       <label class="sl-closeModalButton" onclick="modalClose('point3','opBg3','section_01')">×</label>
       <h2>ポイント3</h2>
       <p>ほげほげほげほげほげほげ</p>
   </section>
   <div id="opBg4" class="ly-opBg ly-nonVisible" onclick="modalClose('point4','opBg4','section_01')"></div>
   <section id="point4"  class="ly-modalNormal ly-nonVisible">
       <label class="sl-closeModalButton" onclick="modalClose('point4','opBg4','section_01')">×</label>
       <h2>ポイント4</h2>
       <p>ほげほげほげほげほげほげ</p>
   </section>
</div>
</body>
</html>

CSS

/* レイアウト */
.ly-center {
   text-align: center;
}

.ly-nonVisible {
   visibility: hidden;
}

.ly-visible {
   visibility: visible;
}

.ly-opBg {
   position: fixed;
   z-index: 10;
   top: 0;
   left: 0;
   width: 100%;
   height: 100%;
   background-color: #000;
   opacity: 0.4;
}

.ly-modalNormal {
   position: absolute;
   top: 50%;
   left: 50%;
   transform: translate(-50%, -50%);
   -webkit-transform: translate(-50%, -50%);
   -ms-transform: translate(-50%, -50%);
   z-index: 20;
   margin: 0 auto;
   width: 70%;
   height: 20rem;
   padding: 3rem;
   background-color: #fff;
   font-size: 1rem;
}

/* スタイル */
.sl-popNormal {
   margin: 0;
   width: 20vw;
   height: 5rem;
   padding: 1rem;
   border: 0;
   background-color: #4169E1;
   color: #fff;
   font-size: 1.5rem;
   -webkit-transition: all 0.3s ease;
   -moz-transition: all 0.3s ease;
   -o-transition: all 0.3s ease;
   transition: all  0.3s ease;
}

.sl-popNormal:hover {
   background-color: #587adf;
}

.sl-closeModalButton {
   position: absolute;
   right: 1em;
   top: 0.5em;
   font-size: 2em;
}

JavaScript

function modalVisible(point_id,opBg_id) {
   var x = document.getElementById(point_id);
   x.classList.remove('ly-nonVisible');
   x.classList.add('ly-visible');
   var y = document.getElementById(opBg_id);
   y.classList.remove('ly-nonVisible');
   y.classList.add('ly-visible');
   window.location.href='#'+point_id;
}

function modalClose(point_id,opBg_id,anchor_id) {
   var x = document.getElementById(point_id);
   x.classList.remove('ly-visible');
   x.classList.add('ly-nonVisible');
   var y = document.getElementById(opBg_id);
   y.classList.remove('ly-visible');
   y.classList.add('ly-nonVisible');
   window.location.href='#'+anchor_id;
}

【補論】JavaScript・jQueryの関数における引数の活用について

今回、私用で急にモーダルウィンドウのJavaScriptによる実装が必要となり俄勉強した。

その過程の中で、JavaScriptやjQueryの関数の中に固有の値(idや直接的なスタイル指定)を使うコードの紹介が多く存在することが気になった。関数は汎用的に繰り返し使用可能なことに魅力があるのであって、1回しか使えないのでは関数である必要がない。

恐らくは、Webサイトで初心者向けコンテンツを執筆している人の本業が実務者ではなくライターだからだと思うのだが、Web用のJavaScript・jQuery記述例を記載一般書籍においても変な記述は多い。実際に現場で使われているコードは企業の中で伝承されていて余り表に出にくい類いの情報なのかもしれない。