LoginSignup
5
5

More than 3 years have passed since last update.

Vueでコンポーネントの外側をクリックしたら閉じたい

Last updated at Posted at 2020-09-08

dependencies

vue: 2.x

目的

コンポーネントの外側、または他の部分をクリックしたら、そのコンポーネントを非表示にしたい。

ModalやDialogのようなコンポーネントはscrim(黒い背景のこと)を持つため、外側をクリックしたら閉じるという動きを作りやすい。実際scrimはコンポーネント内ですし。

そうではなく、Popupなどの軽率にフロートしてなおかつ外側をクリックしたら閉じてほしい、というコンポーネントはそれぞれがdocument.onclickに反応する必要がある。

ググってみたらdirectiveで頑張っている例が多い。

vue-click-outsideのコード
https://github.com/vue-bulma/click-outside/blob/master/index.js

ハンドラを通したりSSRを想定する過程で少しhakkyになっているように見える。

ここではdirectiveの代わりにWrapper Componentでよりシンプルに解決したい。

Wrapper (Popup) Component

Popup.vue
<template>
  <div @click.stop>
    <slot />
  </div>
</template>
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
  methods: {
    onClickOutside(e: Event) {
      this.$emit("click-outside");
    },
  },
  beforeMount() {
    document.addEventListener("click", this.onClickOutside);
  },
  beforeDestroy() {
    document.removeEventListener("click", this.onClickOutside);
  },
});
</script>

解説

beforeMountbeforeDestroyはサーバーサイドレンダリングでは呼ばれない。
そのため、directiveで必要なサーバーか否かの判定は必要無くなる。

ref: API — Vue.js

あと自身がクリックされた時にdocumentへ通知されないように、空の@click.stopを置いている。

Usages

usage.vue
<template>
  <PopOver v-if="popOver.isVisible" @click-outside="togglePopOver">
    <div>{{ contents }}</div>
  </PopOver>
</template>
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
  data() {
    return {
      popOver: { isVisible: true }
    }
  },
  methods: {
    togglePopOver() {
      this.$data.popOver.isVisible = !this.$data.popOver.isVisible;
    },
  },
});
</script>

解説

表示/非表示はv-ifで判定してもいいし、PopOverにpropsを渡して自身が判定する方法にしてもいい。
このサンプルではPopOverは何のスタイルも持たず単なるdivとしてDOM Treeには表示されるので、スタイルは好きに変更すれば良いのでは。

5
5
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
5
5