【Vue3】でアニメーション(Web Animations API)を使ってみた

<!-- wp:paragraph -->
<p>こんにちはSadoです。<br>今回はVue3で開閉処理を行う際に、「<strong>Web Animations API</strong>」(JavaScript)を使ってゆっくり開閉するアニメーションを作成してみます！<br><br>純粋に宣言した CSS とは違って、<strong>Web Animations API</strong>を用いる場合 はプロパティからアニメーション時間を動的に設定することが出来るみたいです。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><a rel="noreferrer noopener" href="https://developer.mozilla.org/ja/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API" data-type="URL" data-id="https://developer.mozilla.org/ja/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API" target="_blank">Web Animations APIについて</a></p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2>やること</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>シンプルに開閉ボタンと開いたときに表示される要素を用意して、そこにアニメーションを付け加えていきたいと思います。</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2>準備</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>表示用のファイル（親）とアニメーション処理を書くファイル（子）に分けておきます。<br><br>Vue3.2の<strong><code><span class="has-inline-color has-vivid-red-color">&lt;script setup&gt;</span></code></strong>構文を使用しております。<br>CSSはTailwindを使用。</p>
<!-- /wp:paragraph -->

```vuejs:Index.vue
<script setup lang="ts">
import Child from "./Child.vue";
import { ref } from "vue";

const open = ref(true);
</script>

<template>
<Child :open="open" />
</template>
```


```vuejs:Child.vue
<script setup lang="ts">
import { ref } from "vue";

const props = defineProps<{
  open: boolean;
}>();

const open = ref(props.open);
</script>

<template>
  <section>
    <button class="bg-gray-300 rounded p-4 mb-5">
      開閉ボタン
    </button>
    
    // デフォルトで開いている状態
    <p class="bg-blue-100 p-10">open</p>
  </section>
</template>
```

開閉ボタンと開閉部分の要素を準備(デフォルトで開いておく)
![スクリーンショット-2022-02-21-13.02.14-1-1024x284.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1040483/329d6e94-808f-ffdf-075c-b8de390f0790.png)

<!-- wp:heading -->
<h2>実際にやってみる</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>ここから開閉処理と、開閉したときのアニメーションを付け加えていきます。</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3>開閉処理追加</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>ここでは、開閉処理をボタン要素をクリックすることで開閉が動作するように処理を追加。</p>
<!-- /wp:paragraph -->

```diff_vue:Child.vue
<script setup lang="ts">
import { ref } from "vue";

const props = defineProps<{
  open: boolean;
}>();

const open = ref(props.open);
</script>

<template>
  <section>
+   <button class="bg-gray-300 rounded p-4 mb-5" @click="open = !open">
      開閉ボタン
    </button>
    
+    // デフォルトで開いている状態
+    <div v-if="open">
+      <p class="bg-blue-100 p-10">open</p>
+    </div>
+    <div v-else></div>
  </section>
</template>
```

<!-- wp:heading {"level":3} -->
<h3>アニメーション追加</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>アニメーションを行いたい要素である、開閉部分のdivタグを<code><span class="has-inline-color has-vivid-red-color"><strong>&lt;transition&gt;</strong></span></code>で囲む。<br>そして、<code><strong><span class="has-inline-color has-vivid-red-color">@enter</span></strong></code>と<code><strong><span class="has-inline-color has-vivid-red-color">@leave</span></strong></code>をつけることでscript内に記述してある、enterとleaveのアニメーション処理が行われる。<br></p>
<!-- /wp:paragraph -->

```diff_vue:Child.vue
<script setup lang="ts">
import { ref } from "vue";

const props = defineProps<{
  open: boolean;
}>();

const open = ref(props.open);

+ // 開くとき
+ async function enter(el: Element, done: () => void) {
+  el.classList.add("overflow-hidden");
+
+  await el.animate(
+    [
+      {
+        height: 0,
+      },
+      {
+        height: `${(el as HTMLElement).offsetHeight}px`,
+      },
+    ],
+    {
+      duration: 500,
+      easing: "ease-out",
+    }
+  ).finished;
+
+  el.classList.remove("overflow-hidden");
+
+  done();
+ }
+
+ // 閉まるとき
+ async function leave(el: Element, done: () => void) {
+  el.classList.add("overflow-hidden");
+
+  await el.animate(
+    [
+      {
+        height: `${(el as HTMLElement).offsetHeight}px`,
+      },
+      {
+        height: 0,
+      },
+    ],
+    {
+      duration: 100,
+      easing: "ease-out",
+    }
+  ).finished;
+
+  el.classList.remove("overflow-hidden");
+
+  done();
+ }
</script>

<template>
  <section>
    <button class="bg-gray-300 rounded p-4 mb-5" @click="open = !open">
      開閉ボタン
    </button>
    
    // デフォルトで開いている状態
+    <transition @enter="enter" @leave="leave">
       <div v-if="open">
         <p class="bg-blue-100 p-10">open</p>
       </div>
       <div v-else></div>
+    </transition>
  </section>
</template>
```

<!-- wp:paragraph -->
<p><code><strong><span class="has-inline-color has-vivid-red-color">height: ${(el as HTMLElement).offsetHeight}px,</span></strong></code>の部分では、開閉する際の要素の高さを取得しています。<br>enter(開くとき)では開く際の要素の高さ(0px)から、今回用意したdiv要素の高さ分までを指定。</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>そして、<code><strong><span class="has-inline-color has-vivid-red-color">duration: 500</span></strong></code>の指定によって、開いたときにゆっくりと開かれているのが確認できました。</p>
<!-- /wp:paragraph -->

```diff_vue:Child.vue
   [ 
      {
        height: 0,
      },
　    {
        height: `${(el as HTMLElement).offsetHeight}px`,
    　},
   ],
　　{
      duration: 500,
      easing: "ease-out",
    }
```

![087e8b6320277a15a0e7842612da8bf6.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1040483/7dafcbb1-43b8-acd1-5a8f-9cbf31069ad5.gif)