概要
モーダルの外側をクリックしたらモーダルを閉じるように実装したい時にdialogタグを利用したら、シンプルに実装できたのでご紹介
何がいいのか
- ダイアログの表示・閉じるなどの関数が用意されている
- CSSを書かなくても最初からダイアログがいい感じに表示される
- ダイアログの外側クリックした際に閉じる制御が簡単
サンプルコード
<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}
/>
<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
ダイアログ表示には2パターンある
今回のサンプルコードでは showModal()
を活用して表示しています。もう1つの表示手段としてはshow()
を使う方法があります。サイト内での使いどころに合わせて制御を調整できるのは便利ですね!
- モードレスダイアログ
show()
:表示中でもモーダルウィンドウ以外の操作ができる - モーダルダイアログ
showModal()
:表示中はモーダルウィンドウ以外の操作ができなくなる
デメリット
- 古いブラウザでは動かない。Safariは2022年3月から15.4以上で動くようになりました。割と最近なので人によっては動かない可能性がありますのでご注意を🙋♂️
まとめ
- モーダルコンポーネント実装にはdialogが楽
- 表示・閉じるの機能が用意されている
- 特にCSSを書かなくてもダイアログをいい感じに表示できる
- ダイアログの表示は2パターン用意されている
- 対応しているブラウザとバージョンは確認しておこう
一部アップデートしました!
dialogの内側判定の処理をもっとスッキリと書けるとアドバイスをいただきましたので、アップデートさせていただきました。
(2022/09/06)
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()
}
})
dialog.addEventListener('click', function (event) {
if (event.target === dialog) { // dialogの内側判定
dialog.close()
}
})
<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自身か否かで判定するように変更いたしました。
dialog表示状態ではdialogの外側は疑似要素 ::backdrop
で覆われていますので、外枠をクリックしても event.target
はdialogと同等として判定されます。逆にdialogの内側をクリックするとevent.target
にはdiv class="inner">
以下が適用されるため、内側をクリックしてもdialogは閉じられずに期待通りの処理になります。
この考え方はダイアログ以外でも要素の内側、外側のクリック判定に活かすことができますので、よろしければ試してみてください🙋♂️