9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-08-22

概要

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

dialog-sample.gif

何がいいのか

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

サンプルコード

Clickable Outside Dialog

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}
/>
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

ダイアログ表示には2パターンある

今回のサンプルコードでは showModal()を活用して表示しています。もう1つの表示手段としてはshow()を使う方法があります。サイト内での使いどころに合わせて制御を調整できるのは便利ですね!

  • モードレスダイアログ show():表示中でもモーダルウィンドウ以外の操作ができる
  • モーダルダイアログshowModal():表示中はモーダルウィンドウ以外の操作ができなくなる

デメリット

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

スクリーンショット 2022-08-22 23.55.10.png

まとめ

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

一部アップデートしました!

dialogの内側判定の処理をもっとスッキリと書けるとアドバイスをいただきましたので、アップデートさせていただきました。
(2022/09/06)

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()
    }
})
after
dialog.addEventListener('click', function (event) {
    if (event.target === dialog) { // dialogの内側判定
        dialog.close()
    }
})
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

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

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

9
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?