dependencies
vue: 2.x
目的
コンポーネントの外側、または他の部分をクリックしたら、そのコンポーネントを非表示にしたい。
ModalやDialogのようなコンポーネントはscrim(黒い背景のこと)を持つため、外側をクリックしたら閉じるという動きを作りやすい。実際scrimはコンポーネント内ですし。
そうではなく、Popupなどの軽率にフロートしてなおかつ外側をクリックしたら閉じてほしい、というコンポーネントはそれぞれがdocument.onclick
に反応する必要がある。
ググってみたらdirectiveで頑張っている例が多い。
- vue-click-outside
- Custom Directive
vue-click-outside
のコード
https://github.com/vue-bulma/click-outside/blob/master/index.js
ハンドラを通したりSSRを想定する過程で少しhakkyになっているように見える。
ここではdirectiveの代わりにWrapper Componentでよりシンプルに解決したい。
Wrapper (Popup) Component
<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>
解説
beforeMount
とbeforeDestroy
はサーバーサイドレンダリングでは呼ばれない。
そのため、directiveで必要なサーバーか否かの判定は必要無くなる。
ref: API — Vue.js
あと自身がクリックされた時にdocumentへ通知されないように、空の@click.stop
を置いている。
Usages
<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には表示されるので、スタイルは好きに変更すれば良いのでは。