【Svelte】外側クリックで閉じられるモーダルをdialogで実装したら楽だった

## 概要
モーダルの外側をクリックしたらモーダルを閉じるように実装したい時にdialogタグを利用したら、シンプルに実装できたのでご紹介

![dialog-sample.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94596/b1525252-84c8-7238-5939-23392fcb6937.gif)




### 何がいいのか
- ダイアログの表示・閉じるなどの関数が用意されている
- CSSを書かなくても最初からダイアログがいい感じに表示される
- ダイアログの外側クリックした際に閉じる制御が簡単





## サンプルコード

[Clickable Outside Dialog](https://svelte.dev/repl/0d63d566865f42339ec23bce570b364f?version=3.49.0)

```svelte:App.svelte
<script>
	import Dialog from './Dialog.svelte';
	
	let dialog
	
	function openDialog() {
    dialog.showModal()
    dialog.addEventListener('click', function (event) {
      if (event.target === dialog) {
        dialog.close()
      }
    })
  }

  function closeDialog() {
    dialog.close()
  }
</script>

<button on:click={openDialog}>
	open
</button>

<Dialog
	bind:dialog
	on:closeDialog={closeDialog}
/>
```

```svelte:Dialog.svelte
<script>
    import { createEventDispatcher } from 'svelte';

	export let dialog // （←ポイント） 要素をバインドすることで親側で表示・閉じるの制御が可能に

	const dispatch = createEventDispatcher()
    function clickClose() {
        dispatch('closeDialog')
    }
</script>

<dialog bind:this={dialog}>
  <div class='inner'>
	<h2>Dialog Title</h2>
	<p>
		テキストが入ります。テキストが入ります。テキストが入ります。テキストが入ります。テキストが入ります。テキストが入ります。テキストが入ります。
	</p>
	<button on:click={clickClose}>
		close
	</button>
  </div>
</dialog>
```



### dialogタグとは
> **HTML の `<dialog>` 要素**は、ダイアログボックスや、消すことができるアラート、インスペクター、サブウィンドウ等のような対話的コンポーネントを表します。
https://developer.mozilla.org/ja/docs/Web/HTML/Element/dialog


### ダイアログ表示には２パターンある
今回のサンプルコードでは `showModal()`を活用して表示しています。もう１つの表示手段としては`show()`を使う方法があります。サイト内での使いどころに合わせて制御を調整できるのは便利ですね！
- モードレスダイアログ `show()`：表示中でもモーダルウィンドウ以外の操作ができる
- モーダルダイアログ`showModal()`：表示中はモーダルウィンドウ以外の操作ができなくなる



### デメリット
- 古いブラウザでは動かない。Safariは2022年3月から15.4以上で動くようになりました。割と最近なので人によっては動かない可能性がありますのでご注意を🙋‍♂️

![スクリーンショット 2022-08-22 23.55.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94596/fe84b2c0-5d2a-86b1-250f-46343068f97e.png)

https://developer.mozilla.org/ja/docs/Web/HTML/Element/dialog#%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%83%BC%E3%81%AE%E4%BA%92%E6%8F%9B%E6%80%A7


## まとめ
- モーダルコンポーネント実装にはdialogが楽
	- 表示・閉じるの機能が用意されている
	- 特にCSSを書かなくてもダイアログをいい感じに表示できる
	- ダイアログの表示は2パターン用意されている
- 対応しているブラウザとバージョンは確認しておこう


### 一部アップデートしました！
:::note info
dialogの内側判定の処理をもっとスッキリと書けるとアドバイスをいただきましたので、アップデートさせていただきました。
（2022/09/06）
:::


``` javascript:before
dialog.addEventListener('click', function (event) {
    let rect = dialog.getBoundingClientRect()
    // dialogの内側判定
    let isInDialog =
    rect.top <= event.clientY &&
    event.clientY <= rect.top + rect.height &&
    rect.left <= event.clientX &&
    event.clientX <= rect.left + rect.width
    if (!isInDialog) {
        dialog.close()
    }
})
```

``` javascript:after
dialog.addEventListener('click', function (event) {
    if (event.target === dialog) { // dialogの内側判定
        dialog.close()
    }
})
```
``` html:after
<dialog bind:this={dialog}>
  <div class='inner'> ←dialogの内側を囲うinnerを追加
	<h2>Dialog Title</h2>
	<p>
		テキストが入ります。テキストが入ります。テキストが入ります。テキストが入ります。テキストが入ります。テキストが入ります。テキストが入ります。
	</p>
	<button on:click={clickClose}>
		close
	</button>
  </div>
</dialog>
```

以前に比べるとかなりスッキリさせることができました！🙌
以前の処理ではクリックされた座標がdialogの内側かどうかを判定するようにしていましたが、新しい実装ではクリックされた対象がdialog自身か否かで判定するように変更いたしました。

![スクリーンショット 2022-09-06 1.34.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94596/376522e2-7224-390f-b812-faad17e2edcd.png)


dialog表示状態ではdialogの外側は疑似要素 `::backdrop` で覆われていますので、外枠をクリックしても　`event.target` はdialogと同等として判定されます。逆にdialogの内側をクリックすると`event.target` には`div class="inner">` 以下が適用されるため、内側をクリックしてもdialogは閉じられずに期待通りの処理になります。

この考え方はダイアログ以外でも要素の内側、外側のクリック判定に活かすことができますので、よろしければ試してみてください🙋‍♂️