要素外クリックで閉じる機能をNuxt3で作ってみた。

要素外クリックで閉じる機能を、
VueUseのonClickOutsideを使って、Nuxt3で実装したので、使い方などを書いていきます。

現在、Nuxt3, Vue3, TypeScriptsで開発をしております。

モーダルではなく、ちょっとしたコンテンツの表示非表示を、実装しておりました。
その際、ボタンとコンテンツ以外をクリックしても、非表示にできないかという提案があったため、VueUseのonClickOutsideを使うことにしました。

イメージこんな感じです。
![onclick.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2889120/20964f09-5690-51ba-ccb8-b3c1581edbdf.gif)

『ボタンとコンテンツ以外をクリックしても、**ボタンをクリックしても**非表示にできる機能』です。

※ボタンをクリックして表示、ばつボタンをクリックで、非表示、要は、ボタンクリックでtoggle式でのコントロールでない場合。それは、もっと簡単にできます。以下をご参考してください。
https://vueuse.org/core/onclickoutside/
https://github.com/vueuse/vueuse/blob/main/packages/core/onClickOutside/demo.vue
詳細必要であれば、コメントで教えていただければ、また書きます。


## VueUseのonClickOutsideを使うまでの経緯

### 経緯①
通常のモーダルでは、「vue-final-modal」のライブラリを使っているので、モーダルでは、モーダル以外をクリックして表示非表示は、自身でコードを書かなくても、その機能は作れていました。
また、モダールの場合は、ボタンをクリックして表示非表示でないので、そもそもがやりたいことが違ってきます。
そのため、「vue-final-modal」のライブラリを使わず、実装したいとなりました。
https://github.com/vue-final/vue-final-modal

### 経緯②
以下の記事を参考に、
https://yuyauver98.me/vue-modal-selfclose/
「@click.self=”イベント名”」を使う方法も検討しました。

ただ、表示非表示をコントロールしたいコンテンツがモーダルではないため、親要素につけても、ごく一部の親要素範囲内で、画面全体を包むwapper的要素が必要となります。
表示非表示をコントロールしたいコンテンツが表示されるたび、その要素を出現させるなど、レイアウト上不自然になることも考慮し、今回は、ライブラリを使うことにしました。
（私自身、まだ未熟者のため、他の方法でしたら、実現できたのかもしれませんが、今回は一旦「@click.self=”イベント名”」はやめました。）

### 経緯③
vue-click-outsideのライブラリを使う方法も検討しました。
https://qiita.com/kambe0331/items/7d2c6ed0553d1f75bae7
この方法に関しては、先輩に相談したところ、VueUseで使いたい機能があるから、そっちでどうか？という提案があったため、使わない方法になりました。
（ライブラリを安易に増やすと、ライブラリ自体のバージョンアップやエラーの対応があった際、対応が増えることをできるだけ避けたい。ということもあり、ライブラリを使用検討するということは慎重に対応する必要があります。）

## では、使い方を書いていきます！

### ①まずインストールしていきます。
```
npm i -D @vueuse/components
```


※他のVueUseの機能を使いたい場合の方はこちらもご覧ください。
（onClickOutsideのみ使いたい場合は、②へ）

他のVueUseの機能を使いたい場合は、以下のインストールも必要です。
@vueuse/nuxtをインストールすると、importしなくても、機能が使えるっぽいのですが、公式のスタートガイドには2つインストールするように書かれています。
https://vueuse.org/guide/
```
npm i -D @vueuse/nuxt @vueuse/core
```
また、以下も記述してください。（onClickOutsideのみの場合は、必要ありません。）
```
// nuxt.config.ts
export default {
  modules: [
    '@vueuse/nuxt',
  ],
}
```

### ②使用したいページでimportします。
```
import { vOnClickOutside } from '@vueuse/components';
```

### ③onClickOutside使っていきます！
ポイントは2つです。
・**<span>@</span>click.stop**="state.display = !state.display"
：<span>@</span>stopの.stopは、親のイベントを伝搬しないためのものです。
これがないと、表示ができなくなります。

・**v-on-click-outside.bubble**="() => state.display = false"
：ボタンをクリックしても表示非表示させたい。（toggle的な感じ）場合は、「.bubble」が必要です。
これがないと、ボタンをクリックしても閉じなくなってしまいます。

```
<template>
  <button @click.stop="state.display = !state.display">
    buttonClick!!  
  </button>
  <div class="content" 
    v-if="state.display" 
    v-on-click-outside.bubble="() => state.display = false">
    Hello World
  </div>
</template>
```

こんな感じで使えます。

## ちょっと詰まったこと
表示非表示を「v-show」で制御している場合は、使えないです。
（当然といえば当然。。。）
v-showは、DOMがHTMLに残るため、一瞬表示されて、すぐ非表示なるという面白いものが作れます。
![ああ.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2889120/3785d33a-6d09-77b1-87c1-b43642c56b78.gif)

## コード全文
必要かどうかわかりませんが、コード全文も載せておきます。 styleはお好きにどうぞ。
```
<script setup lang="ts">
import { vOnClickOutside } from '@vueuse/components';

const state = reactive({
  display: false,
});

</script>
<template>
  <button @click.stop="state.display = !state.display">
    buttonClick!!  
  </button>
  <div class="content" 
    v-show="state.display" 
    v-on-click-outside.bubble="() => state.display = false">
    Hello World
  </div>
</template>
```