vue3でオンボーディングツアーを作成する (vue-shepherd)

vue3でオンボーディングツアー機能(?)を作ってみました。
(オンボーディングツアー機能：ヌルヌルとポッポアップがついてきてアプリの使い方を教えてくれるやつです)
ライブラリとしてvue-shepherdを使用しました。
shepherd.jsのvue3用のラッパーライブラリです。そのライブラリの使い方のメモになります。
誰かの参考になれば幸いです！:grinning:

動きです↓
![ezgif-5-790dc453e1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542326/4d618a8d-b28e-11e4-a577-b65484e4a8a5.gif)


公式サイト：https://shepherdjs.dev/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542326/ceff1873-2deb-22ca-7133-e90d7bcb4f3a.png)

やりたいことは簡単に大体できるので超便利です！
ただ思ったこととして、そのまま使うと、
トリガーとポップアップが以下の画像のように被る&トリガーを囲む枠がぴったりすぎるので
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542326/310aad9e-1aae-2519-ec17-146779be45ca.png)
ポップアップの位置を変更する&トリガーを囲む枠にマージンをつけるなどの
オプションを設定した方が見た目が良くなると思いました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542326/03cd1736-567f-a1ac-a9ce-cddc77a9418e.png)
そのオプションをつけるコードはこんな感じです。
offsetをfloating-uiからインポートするところで少し時間がかかりました。
(参考：https://github.com/shipshapecode/vue-shepherd/issues/199)

```vue:app.vue
<script setup>
import { offset } from "@floating-ui/core";

// 一部省略

onMounted(() => {
  tour.addStep({
    text: "3rd Modal with modified style",
    attachTo: {
      element: ".third-trigger",
      on: "bottom",
    },
    modalOverlayOpeningPadding: 5, // ←これ
    modalOverlayOpeningRadius: 5, // ←これ
    floatingUIOptions: {          // ←これ
      middleware: [offset({ mainAxis: 20, crossAxis: 0 })],
    },
    // 一部省略
  });
});
</script>
```




ちなみに実際の動きを見たい方はごちらをご覧ください。
CodeSandBox：https://codesandbox.io/s/fervent-wood-ol05ju?file=/src/App.vue
全体のサンプルコード↓
```vue:app.vue
<template>
  <link
    rel="stylesheet"
    href="https://cdn.jsdelivr.net/npm/shepherd.js@8.3.1/dist/css/shepherd.css"
  />

  <img alt="Vue logo" src="./assets/logo.png" />
  <div ref="el">
    <div class="first-trigger">1st trigger</div>
    <div>
      <span class="second-trigger">2nd trigger</span>
    </div>
    <div>
      <span class="third-trigger">3rd trigger</span>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from "vue";
import { useShepherd } from "vue-shepherd";
import { offset } from "@floating-ui/core";

const el = ref(null);
const tour = useShepherd({
  useModalOverlay: true,
});

onMounted(() => {
  tour.addStep({
    text: "1st Modal",
    buttons: [
      {
        action() {
          return this.cancel();
        },
        classes: "shepherd-button-secondary",
        text: "Skip",
      },
      {
        action() {
          return this.next();
        },
        text: "Next",
      },
    ],
  });
  tour.addStep({
    text: "2nd Modal with default popover style",
    attachTo: {
      element: ".second-trigger",
      on: "bottom",
    },
    buttons: [
      {
        action() {
          return this.back();
        },
        classes: "shepherd-button-secondary",
        text: "Back",
      },
      {
        action() {
          return this.next();
        },
        text: "Next",
      },
    ],
  });
  tour.addStep({
    text: "3rd Modal with modified style",
    attachTo: {
      element: ".third-trigger",
      on: "bottom",
    },
    modalOverlayOpeningPadding: 5,
    modalOverlayOpeningRadius: 5,
    floatingUIOptions: {
      middleware: [offset({ mainAxis: 20, crossAxis: 0 })],
    },
    buttons: [
      {
        action() {
          return this.back();
        },
        classes: "shepherd-button-secondary",
        text: "Back",
      },
      {
        action() {
          return this.complete();
        },
        text: "Done!",
      },
    ],
  });

  tour.start();
});
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
```

### 追記：progress barをつける
shepherd.jsのドキュメントとは、要素の指定方法が違うので注意。
(Shepherd.activeTour? -> tour)
```app.vue
const tour = useShepherd({
    defaultStepOptions: {
        ~~~
        when: {
            show() {
                const currentStep = tour.getCurrentStep();
                const currentStepElement = currentStep?.getElement();
                const footer = currentStepElement?.querySelector('.shepherd-footer');
                const progress = document.createElement('span');
                progress.className = 'shepherd-progress';
                const progress_percentage = (tour.steps.indexOf(currentStep) + 1) / tour.steps.length * 100;
                progress.innerHTML = `<div class="w-full bg-gray-200 rounded-full h-2.5 dark:bg-gray-700">
                                        <div class="bg-blue-600 h-2.5 rounded-full" style="width: ` + progress_percentage + `%"></div>
                                      </div>`
                footer?.insertBefore(progress, currentStepElement.querySelector('.shepherd-button:first-child'));
            }
        }
    },
});
```