[基礎]コピペしてすぐ使える! 生のJsでモーダルを自作してみた

# はじめに
本記事で記述する内容です。
+ プラグインを使わない生のJsでモーダル作成しました。
+ 簡単なコードなのでコピペしてすぐ使えると思います。
+ なるべくOOP(クラス記法)で書いてます...今後改修予定あり
+ モーダル表示後に簡単な動作を加えてModalを閉じる実装にしました
+ 無駄な記述が多かったので修正しました(2022_8_20)


## 環境
+ エディタ: VS Code バージョン: 1.70.2
+ Live Serverv 5.7.5



## 対象読者様
+ モーダルをプラグインなしで使い回したいてみたい方
+ JsとDOM操作、CSSに関連する基礎あたりの総復習をしたい方
+ addEventlistener の扱い。イベントリスナー,eの扱いに慣れたい方
+ React行くぞって方(私)

### さっそくですがデモ動画はこちら(音量注意、すいません)

https://youtu.be/5z5qrYVRhaM

モーダルが表示される前のhtmlは
```html
モーダルの受け入れ元のhtml
            <section class="modal_list">
                <h2>モーダル用</h2>
                <div class="modal_list__items">
                    <div id="easyModal">
                        <div class="modal-content">
                            <div class="modal-header"></div>
                            <div class="modal-body">
                                <!-- ここに実際のモーダルのhtmlが挿入される -->
                            </div>
                        </div>
                    </div>
                    <a class="image" href="#modal1"><img src="../images/dish_1.jpg"><span>季節の野菜</span></a>
                    <a class="image" href="#modal2"><img src="../images/dish_2.jpg"><span>野菜スープ</span></a>
                    <a class="image" href="#modal3"><img src="../images/dish_3.jpg"><span>パン</span></a>
                    <a class="image" href="#modal4"><img src="../images/dish_4.jpg"><span>イタリアン</span></a>
                </div>
            </section>

```
この中のmodal-bodyに画像クリックした時のモーダル要素が挿入されます。


```html
モーダル表示html
<!-- modal template -->
<!--デフォルトでdisplaynone-->
<div id="modal1">
    <div class="modal-header">
        <h1>季節の彩り前菜</h1>
        <span data="aa" class="modalClose">×</span>
    </div>
        <img class="active" src="../images/dish_1.jpg">
        <p>様々な食材を使用し作り上げた色とりどりの前菜はテーブルをカラフルに彩ります♪</p>
        <button class="btn" data="イタリアン">シェア</button>
</div>
<div id="modal2">
    <div class="modal-header">
        <h1>野菜スープ</h1>
        <span class="modalClose">×</span>
    </div>
    <img class="active" src="../images/dish_2.jpg">
    <p>柔らかく蒸したあとはホイッパーで混ぜるだけの簡単レシピです♪かぼちゃは、ビタミンやミネラルが豊富なので美容や冷え性に良いですよ♪</p>
    <button class="btn" data="フレンチ">シェア</button>
</div>
<div id="modal3">
    <div class="modal-header">
        <h1>ヘルシーパン</h1>
        <span class="modalClose">×</span>
    </div>
    <img class="active" src="../images/dish_3.jpg">
    <p>米粉を使ったさっくりとした軽い食感のパンです♪米粉は日本の主食のお米で作られており、ヘルシーで口当たりもいいですよ♪</p>
    <button class="btn" data="ヘルシーパン">シェア</button>
</div>
<div id="modal4">
    <div class="modal-header">
        <h1>エビ料理</h1>
        <span class="modalClose">×</span>
    </div>
    <img class="active" src="../images/dish_4.jpg">
    <p>エビがサラダやスープ、ごはんなどに入っていると、テンションが上がりますよね♪ただ、エビは意外に調理が難しいという声も...。実はあるテクニックを使うだけでエビがプリップリに仕上がっちゃうんです♪</p>
    <button class="btn" data="エビ料理">シェア</button>
</div>

<!--/modal template -->
```

まずモーダルに関係していない部分のCSSはこちら。

```css

/* section幅に均等にdiv要素を配置 */
.modal_list__items {
  display: flex;
  width: 740px;
  justify-content: space-between;
  margin-left: 40px;
  list-style: none;
}

/* 親のdiv要素の大きさに画像がフィットするように設定 */
.modal_list__items img {
  display: block;
  object-fit: cover;
  width: 100%;
}
```

モーダルが開いた時のスタイリングはこちら。
大事な部分は、モーダルに挿入される要素のsytyleはデフォルトでnoneというところですかね。
あと、overflow: auto;が重要です。 scrollでも今のところ動作的には大きな変化はありませんでしたが...。

```css

/*初期状態 */
#easyModal {
  display: none;/* Jsでボタン押された時にblockにする */
  position: fixed;/* positionの位置はお好みで */
  z-index: 1;
  left: 0;
  top: -38px;
  height: 100%;
  width: 100%;
  overflow: auto;/*ここが少しややこしいのですが、設定するとモーダルが大きく、見切れる場合に、scrollバーを出してくれます。*/
  background-color: rgba(0,0,0,0.5);
}

.modal-content {
  background-color: #f4f4f4;
  margin: 31px auto;
  width: 57%;
  box-shadow: 0 5px 8px 0 rgba(0,0,0,0.2),0 7px 20px 0 rgba(0,0,0,0.17);
  animation-name: modalopen;
  animation-duration: 1s;
}

@keyframes modalopen {
  from {opacity: 0}
  to {opacity: 1}
}

.modal-header h1 {
  margin: 1rem 0;
}

.modal-header {
  background: lightblue;
  padding: 3px 15px;
  display: flex;
  justify-content: space-between;
}

.modalClose {
  font-size: 2rem;
}

.modalClose:hover {
  cursor: pointer;
}

.modal-body {
  padding: 20px 40px;
  color: black;
  z-index: 2;/* オープンしたあとクローズするための動きのため追加 */
}

/* */
.active {
  object-fit: cover;
  height: 80%;
  width: 100%;
}

/* デフォルトでは全てdisplayがnone */
#modal1,
#modal2,
#modal3,
#modal4 {
  display: none;
}

```

Jsを書くための前準備は終了しました。

```javascript

class MyModal {
  constructor(el) {
    this.elements = document.querySelectorAll(el); //今回は'.image'を複数取得
    this.modal = document.querySelector('#easyModal'); //modalの全体を取得
    this._addEvent(); //プライベートメソッドにしてaddEventの記述を整える
  }
  _addEvent() {
    //あるオブジェクトのaddEventlistnerにイベントを登録する際のthisの参照先に気をつける。
    //今回はthisをMymodalのインスタンスに束縛したいので、bind(this)と記述する必要あり。
    window.addEventListener('click', this._outsideClose.bind(this)); //

    //各elに関するイベントを登録。clickでopenメソッドが発火し、openがイベントリスナーとして扱えるようになりました。後で「e」を引数にとり色々実装します。
    this.elements.forEach(el => {
      el.addEventListener('click', this._open.bind(this));
    });
  }
  // openメソッドはイベントオブジェクトのeを引数に取れるので、
  _open(e) {
    let href = e.currentTarget.href; //クリックしたimgのa要素のhrefの値を取得。
    let element_id = href.substr(href.indexOf('#') + 1); //これで例えば「modal1」などの文字列を取得できる。
    //取得したmodal1というidを元にして、器部分のhtmlを差し替える。
    document.querySelector('.modal-body').innerHTML = document.getElementById(element_id).innerHTML;
    
    //ボタンの機能を追加する。
    //シェアボタンを取得
    let sharbtn = document.querySelector('.modal-body .btn');
    //シェアボタンを押すとアラートが表示されるが、メッセージをmodal毎に変化させたい。
    //そのためdata属性をシェアbtnに仕込んでおき、メッセージの切り替えをする。
    sharbtn.addEventListener('click', (e) => {
      alert(`${e.currentTarget.getAttribute('data')}のレシピをシェアしました♪`);
        this._close();//アラートはokを押すまでその処理を追えないので、closeメソッドをココに仕込む。
    });
    ///×ボタンにcloseメソッドを追加する必要がある。
    let clonedClose = document.querySelector('.modal-body .modalClose');
    clonedClose.addEventListener('click', this._close.bind(this));
    this.modal.style.display = 'block';
  }
  _close() {
    this.modal.style.display = 'none';
  }
  //クリックされたオブジェクトが#easymodalだったら閉じる。#easymodalのz-indexは1、modal-bodyのz-indexは2なのでここをクリックしても何も起きない。
  _outsideClose(e) {
    if (e.target === this.modal) {
      this.modal.style.display = 'none';
    }
  }
}

new MyModal('.image');

```

## まとめ

今回、Class記法で実装してみました。他にもさまざまなやり方があると思うのですが、個人的には私のようなレベルでも自作できたので、簡単なんじゃないかなと思います。
+ 説明や解釈がおかしい！
+ 動作がおかしい！
+ ここの記述が間違っている！
+ こっちの方がいい:relaxed:

という叱咤激励があると嬉しいです。