TypeScriptでモーダル（ポップアップ画像）を実装する方法

# 巷でよく見るイメージモーダル（画像表示→非表示）をTypeScriptで作ってみた
LPやサイトコンテンツでもモーダル機能はよく利用されています。
JavaScriptでの実装例はQiitaを含めネットでも多く記事がありますが、TypeScriptでの実装例は少なかったのでまとめてみました。

機能としては以下の内容になります。

- ページ読み込み時にモーダル画像が表示※今回はpixabayからフリー素材を転用
- メインコンテンツが透けて見える
- 画像 or グレーアウトした背景部分をクリックすると画像が非表示になる


![モーダルサンプル.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1824740/d6f92b11-fe25-0cdd-b10f-7f36286f500b.gif)


## 環境構築

今回はyarnとViteを使って構築しております。

Viteに関しては詳細は割愛しますが開発効率面でざっくり以下のメリットがあるようです。
- Goで書かれたesbuildを利用して依存関係の事前ビルドを行う（JavaScript ベースよりも 10 倍から 100 倍高速）
- Native ESM（ビルド後にもES Modulesが利用される）を利用することでブラウザ側でimport/exportを解析できるためバンドルが不要になる。

:::note warn
お手元でTypeScriptが動く環境であれば特に環境構築部分は飛ばして頂いて問題ありません。
:::


### npmを確認

```c
# node.jsのバージョンを確認
node -v

# npmのバージョンを確認
npm -v
```

### npm 経由でyarnをインストール

```c
# npm 経由でyarnをインストール
npm install -g yarn

# yarnのバージョンを確認
yarn -v

# yarnの初期設定
yarn init
```

### ViteでTypeScriptプロジェクト作成

```
yarn create vite
```

### TypeScriptを選択

```
? Project name: › hello-vite # (1)プロジェクト名を入力
? Select a framework: › - Use arrow-keys. Return to submit.
❯   vanilla # (2)フレームワークを選択（vanilla = フレームワークを使わない）
    vue
    react
    preact
    lit-element
    svelte
? Select a variant: › - Use arrow-keys. Return to submit.
    vanilla 
❯   vanilla-ts # (3)TypeScriptを選択
```

### nodeパッケージをインストール

```
yarn install
```

## モーダル部分

実装部分は以下の通りです。

- index.htmlに予めモーダルを画像を用意（`visibility: hidden;`で非表示）
- ユーザーのクリックイベントで`visibility: visible;`をつけて表示する

```typescript:main.ts
import './style.css'

// ページが読み込まれたタイミングで実行
window.onload = () => {
  const popup = document.getElementById('popup') as HTMLDivElement | null;

  // モーダル画像表示
  popup!.classList.add('show');

  // 各要素取得
  const blackBg = document.getElementById('modalBackground') as HTMLDivElement | null;
  const modalWindow = document.getElementById('modalWindow') as HTMLDivElement | null;

  // 各要素にイベントを設定
  closePopUp(blackBg);
  closePopUp(modalWindow);

  // クリック発生でモーダル画像を非表示にする
  function closePopUp(elem: HTMLElement | null) {
    if (!elem) return;
    elem.addEventListener('click', () => {
      popup?.classList.remove('show');
    });
  }
}
```

```html:index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="favicon.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
  </head>
  <body>
    <div id="app">
    <h1>にゃんこ画像のモーダルサンプル</h1>
    <p>にゃんこ。にゃんこ。にゃんこ。にゃんこ。にゃんこ。にゃんこ。</p>
    <p>にゃんこ。にゃんこ。にゃんこ。にゃんこ。にゃんこ。にゃんこ。</p>
    <p>にゃんこ。にゃんこ。にゃんこ。にゃんこ。にゃんこ。にゃんこ。</p>
    </div>
    </div>
    <!-- モーダル画像部分はじまり -->
    <div class="popup" id="popup">
      <div class="modalWindow" id="modalWindow">
        <!-- 任意のモーダル画像を設定 -->
        <img src="https://cdn.pixabay.com/photo/2014/04/13/20/49/cat-323262_960_720.jpg" alt="フリー素材の猫画像">
      </div>
      <div class="modalWrapper" id="modalBackground"></div>
    </div>
    <!-- モーダル画像部分おわり -->
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>
```

```css:style.css
.popup {
    position: fixed;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    z-index: 9999;
    opacity: 0;
    visibility: hidden;
    transition: .6s;
  }

  /* クリックイベントでこのスタイルを取り除く */
  .popup.show {
    opacity: 1;
    visibility: visible;
  }
  
  .modalWrapper {
    width: 100%;
    height: 100%;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    background: rgba(0, 0, 0, .7);
    position: fixed;
    z-index: 5;
    cursor: pointer;
  }
  .modalWindow {
    width: 750px;
    height: 460px;
    z-index: 20;
    position: absolute;
    top: 50%;
    left: 50%;
    transform : translate(-50%, -50%);
    cursor: pointer;
  }
  
  .modalWindow img {
    width: 100%;
    height: auto;
  }
  
```

### ローカルサーバー起動で確認

```
yarn dev
```

```
yarn vite
```


## 参考
主に環境構築で参考にしました。

https://ja.vitejs.dev/guide/why.html#%E3%81%AA%E3%81%9C-vite-%E3%81%AA%E3%81%AE%E3%81%8B

https://yarnpkg.com/

https://www.typescriptlang.org/

https://ics.media/entry/210708/

https://qiita.com/suisui654/items/1b89446e03991c7c2c3d

https://zenn.dev/sykmhmh/articles/ff09bea2cf7026

https://zenn.dev/uhyo/articles/what-is-native-esm-era