Modalって作るの難しい・・・諳んじれますか？


ふと気になって調べること数十回。自分の中で区切りをつけたいので記事にまとめます！

あと、諳んじる必要は全く無いと思っています！


## 環境

BrowserがあればOKです。

一応、[リポジトリ]()にNode プロジェクトとしても作ってあります。`npm run start`で動きます。


## 実装

### Modal構成要素

- ダイアログ中央表示
- Overlay
- 表示・非表示切り替え

では、順番に実装していきましょう。

### ダイアログ中央表示

絶対位置で、ダイアログを中央に配置します。

```html:index.html
<!DOCTYPE html>
<head>
  <title>Modal App</title>
  <link rel="stylesheet" href="style.css" />
</head>
<body>
  <div id="dialog">Dialog</div>
</body>
```

```css:style.css
#dialog {
  position: fixed; /* 絶対位置で表示 */
  top: 50%; /* 上端を中央に */
  left: 50%; /* 左端を中央に */
  /* 上記までで、エレメント左上が中央に位置することになる*/
  /* 再配置を行うtranslateで、エレメントをその大きさの半分ずつ左と上にずらす*/
  /* これにより、エレメントの中心がWindowの中心に位置することになる。*/
  transform: translate(-50%, -50%);
  width: 30%; 
  height: 30%;
  border: 1px solid black;
  background-color: lightblue;
}
```

## Overlay

ダイアログの**背面**にシートを敷くイメージです。

```diff_html:index.html
<!DOCTYPE html>
<head>
  <title>Modal App</title>
  <link rel="stylesheet" href="style.css" />
</head>
<body>
  <div id="dialog">Dialog</div>
+ <div id="overlay"></div>
</body>
```

```diff_css:style.css
#dialog {
  position: fixed; /* 絶対位置で表示 */
  top: 50%; /* 上端を中央に */
  left: 50%; /* 左端を中央に */
  /* 上記までで、エレメント左上が中央に位置することになる*/
  /* 再配置を行うtranslateで、エレメントをその大きさの半分ずつ左と上にずらす*/
  /* これにより、エレメントの中心がWindowの中心に位置することになる。*/
+ z-index: 1000; /* 他の要素よりも手前に表示する */
  transform: translate(-50%, -50%);
  width: 30%; 
  height: 30%;
  border: 1px solid black;
  background-color: lightblue;
}

/* 追加 */
#overlay {
  position: fixed; /* 絶対位置で表示 */
  /* 画面全体を覆う */
  /* Browser windowの左上から、縦横一面の大きさを設定し配置*/
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 999; /* 他の要素よりも手前に表示する。ただし、ダイアログの背面 */
  background-color: lightgray;
}
```

## 表示・非表示

JavaScriptによる制御を組み込みます

また、ダイアログ表示にちょっとした動きを取り入れます。

```diff_html:index.html
<!DOCTYPE html>
<head>
  <title>Modal App</title>
  <link rel="stylesheet" href="style.css" />
</head>
<body>
  <div id="dialog">Dialog</div>
  <div id="overlay" />
+ <button id="open-button">Open</button>
+ <script src="index.js"></script>
</body>
```

```diff_css:style.css
#dialog {
+ /* 初期状態では非表示 */
+ opacity: 0; 
+ visibility: hidden; 
+ /* 0.3sかけて、表示・非表示が切り替わります。 */
+ transition: all 0.3s ease-out;
  position: fixed; /* 絶対位置で表示 */
  top: 50%; /* 上端を中央に */
  left: 50%; /* 左端を中央に */
  /* 上記までで、エレメント左上が中央に位置することになる */
  /* 再配置を行うtranslateで、エレメントをその大きさの半分ずつ左と上にずらす */
  /* これにより、エレメントの中心がWindowの中心に位置することになる。*/
  z-index: 1000; /* 他の要素よりも手前に表示する */
  transform: translate(-50%, -50%);
  width: 30%; 
  height: 30%;
  border: 1px solid black;
  background-color: lightblue;
}

#overlay {
+ /* 初期状態では非表示 */
+ /* visibility: hidden;を設定することでOpenボタンを活性にできます。*/
+ opacity: 0; 
+ visibility: hidden; 
+ transition: all 0.3s ease-out;
  position: fixed; /* 絶対位置で表示 */
  /* 画面全体を覆う */
  /* Browser windowの左上から、縦横一面の大きさを設定し配置　*/
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 999; /* 他の要素よりも手前に表示する。ただし、ダイアログの背面 */
  background-color: lightgray;
}

/* 追加 */
#open-button {
  cursor: pointer;
  border: 1px solid #000;
  margin: 10px;
}
```

```javascript:index.js
// エレメントを取得
const dialog = document.getElementById('dialog');
const overlay = document.getElementById('overlay');
const openButton = document.getElementById('open-button');

// Scrollを固定するためのエレメント
const body = document.body;

// 表示処理
openButton.addEventListener('click', open);
function open() {
  dialog.style.opacity = 1;
  dialog.style.visibility = 'visible';
  overlay.style.opacity = 1;
  overlay.style.visibility = 'visible';
  body.style.overflow = 'hidden';
}

// 非表示処理
overlay.addEventListener('click', close);
function close() {
  dialog.style.opacity = 0;
  dialog.style.visibility = 'hidden';
  overlay.style.opacity = 0;
  overlay.style.visibility = 'hidden';
  body.style.overflow = 'auto';
}
```

## 最後に

この仕組みを理解していると、アカウント登録ダイアログがPopupして、続きが見れない系のモーダルを・・・

ということもできてしまいます。

**Browser上のリソースは、基本的に見られてしまう・変更できてしまう**ということは忘れたくないですね。

また、実際のアプリケーションにおいては、"車輪の再発明"は必要ないです。

可能な限りよく使われているライブラリを使用しましょう！

あと、不勉強でしたが、[Dialog](https://developer.mozilla.org/ja/docs/Web/HTML/Element/dialog)タグも使えるようになっているようです！

## リポジトリ

[simple-node-app](https://github.com/HIppei/simple-node-app)