自身のポートフォリオに組み込んだ「アニメーションキュー」のご紹介

# はじめに
本記事は自身のポートフォリオに組み込んだ自作機能「アニメーションキュー」を紹介するものです。組み込みたい方はぜひ組み込んでみてください。

## 環境
` npx create-nuxt-app`を使用。(ver.2.0.0)

# 私の自作した「アニメーションキュー」とは？
文字通りアニメーションをキューにため込み、一つのアニメーションを実行、終了、次のアニメーションを実行、終了...を繰り返す機能です。
![hogehoge.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405090/653919e8-1e21-d8fd-23f6-59def335320b.gif)
こんな感じでスクロールに合わせて順番にアニメーションを実行するということが可能です。

アニメーションキューのイメージは以下の通りです。
![Untitled Diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405090/5b306487-308c-2658-84ee-495023be313e.png)
先に追加されたアニメーションから実行されるという至ってシンプルなシステムです。

# 本題
早速本題に入ります。
このシステムの流れとしては以下のとおりです。

1. CSSでアニメーションを定義。
2. VueXで、アニメーション登録の流れを定義。
3. 各コンポーネントでアニメーションを実行したいdomにrefを設定し、mountedで実際にアニメーション登録。

## CSSでアニメーション定義

ではまず、CSSから。
今回は簡単なフェードインを実装した。
assets内に以下のファイルを作成。

```css

@keyframes scaleup-fadein{
  0%{
    opacity: 0;
    transform: scale(0.8);
  }
  100%{
    opacity: 1;
    transform: scale(1.0);
  }
}
```

これを作成したら、`nuxt.config.js`のmodule.exportにて`css: [<作成したファイル名>]`としてcssを共通のものに登録します。

### VueX(store)に設定ファイルを作成

次にstoreディレクトリに以下のファイルを作成します。

```js
  
export const state = () => ({
  //アニメーションを保管するキュー
  //オブジェクト型のため、セクションごとに別設定を行える。
  animeQue: {}
});

export const mutations = {
  /**
   * アニメーションをキューに追加する。
   * @param {*} state
   * @param {*} param1
   */
  pushAnime(state, { regist, anime, dom }) {
    // アニメーション開始前にDOMの不可視
    dom.style.opacity = "0";
　　// アニメーション終了後にDOMの可視化状態を維持
    dom.addEventListener("animationend", function() {
      dom.style.opacity = "1";
    });

    //まだキューに何もない場合。
    if (state.animeQue[regist] === void 0) {
      // 新たなアニメーションキューを生成。
      state.animeQue[regist] = [];
      //スクロールがどの位置に来たら開始するかを設定する。
      if (anime.distance != void 0)
        window.addEventListener("scroll", function() {
          if (anime.distance > dom.getBoundingClientRect().top) {
            // 指定の位置まで来たため、アニメーションを実行する。
            dom.style.animationName = anime.name;
          }
        });
      // スクロール位置の設定がない場合はすぐにスタート
      else dom.style.animationName = anime.name;
    }

    //一つ以上のキューにある場合
    else {
      //キューの一番最後にアニメーションを追加し、
      //前のアニメーションが終了したら発動するよう設定する
      state.animeStack[regist][
        state.animeStack[regist].length - 1
      ].addEventListener("animationend", function() {
        dom.style.animationName = anime.name;
      });
    }
    // アニメーションの感覚時間を決定し、実際にプッシュ
    dom.style.animationDuration = anime.duration + "s";
    state.animeStack[regist].push(dom);
  }
};
```
これがアニメーションキューの本体です。

登録名ごとにアニメーションキューが生成されます。

また、アニメーションについて、
・ キューが生成された場合はアニメーションのスタートタイミングの設定(座標指定がコンポーネントからあった場合は設定し、指定がなければすぐ開始。)
・ キューに新たなアニメーションが追加された場合は、ひとつ前のアニメーションが終了されたときに追加したアニメーションが実行されるように設定。
という決まりになっています。


## 各コンポーネントでアニメーションキューの追加設定をする。

アニメーションを実施したいコンポーネントに設定することで、それぞれのアニメーションを順番に動かすことが可能です。

コード例は以下の通りです。

```vue

<template>
  <h2 ref="secondTitle" class="second-title">
    <span class="second-title-frame"></span>
    <span class="second-title-frame"></span>
    <span class="second-title-frame"></span>
    <span class="second-title-frame"></span>

    <span class="title">{{ title }}</span>
    <span class="subtitle">{{ subtitle }}</span>
  </h2>
</template>

<script>
export default {
  props: {
    title: String,
    subtitle: String,
    animeregist: String,
  },
  mounted() {
    this.$store.commit("anime/pushAnime", {
      regist: this.animeregist,
      anime: {
        name: "scaleup-fadein",
        duration: 0.3,
        distance: 600,
      },
      dom: this.$refs.secondTitle,
    });
  },
};
</script>

```

mountedにてアニメーションキューに登録をしている。storeにanime.jsという名前で保存したためcommit先が`anime/pushAnime`となっています。
commitしているオブジェクト詳細については以下のとおりです。

```
{
  regist : "アニメーションキュー名/string",
  anime:{
    name:"アニメーション名(CSSで設定したアニメーション)/string"
    duration:"アニメーションの実行期間/double"
    distance:"アニメーションを開始する際のスクロール座標/int"
  },
  dom:"指定するDOM/DOMElement"
}
```
今回の場合だとSecondTitleというコンポーネントであるため、propsにてアニメーションキューの情報を受け取ることとし、pagesで実際の値を渡すようにしています。
こうすることで、同じコンポーネントを使っていても別々のアニメーションキューに登録することが可能であります。

# 最後に

どうでしょう。使い道はありそうでしょうか。すでに既出な気もしますが、自身で1から作ってみたため、本記事で紹介してみました。ぜひシステムに組み込んでみてください。

本記事に対して、わかりづらい等のご意見あれば、お待ちしております。

では皆様、また会いましょう。