Vue.js の transition を用いてインタラクティブな「泡」のアニメーションを作った

泡の需要ない気がするけど...!

## 概要
業務では、どちらかというとReact.jsを触ることの方が多いのですが、たまたまコーポレートサイトでVue.jsを使う機会があったのでまとめてみました。
コーポレートサイトの見た目がちょっと寂しかったので少しアニメーションを入れてみることになり、泡をぷかぷか浮かべるアニメーションを作成してみました！
Vue.js 便利...

## 参考
Vue.js のドキュメントを参考にしました！
https://jp.vuejs.org/v2/guide/transitions.html

## 導入
### transitionによる CSS アニメーション
Vue は、transition ラッパーコンポーネントを提供しています。このコンポーネントは、次のコンテキストにある要素やコンポーネントに entering/leaving トランジションを追加できます！
つまり、

```
<div id="demo">
  <button v-on:click="show = !show">
    Toggle
  </button>
  <transition>
    <p v-if="show">hello</p>
  </transition>
</div>
```
このように条件付きで hello と表示させる際には、enter/leave トランジションのために
v-enter,v-enter-active, v-enter-to,v-leave,v-leave-active, v-leave-to
というクラスが適用されます。
このクラスを用いて簡単にCSS トランジションを実現できるんです。
例えば

```
.fade-enter-active, .fade-leave-active {
  transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
  opacity: 0;
}
```
以上のようにcss を適用させるとトランジションのタイミングごとにクラスがふよされるのでふわっと文字が浮かび上がるアニメーションができます。

### トランジション期間の設定
デフォルトではtransitionendイベントにフックすることもできますが、トランジション期間の設定をもっと明示的に設定したい場合もあるでしょう。
そんな時は、属性で JavaScript フックを定義することができます
例えば、vueファイルのtemplateで、

```
<transition
  v-on:before-enter="beforeEnter"
  v-on:enter="enter"
  v-on:after-enter="afterEnter"
  v-on:enter-cancelled="enterCancelled"

  v-on:before-leave="beforeLeave"
  v-on:leave="leave"
  v-on:after-leave="afterLeave"
  v-on:leave-cancelled="leaveCancelled"
>
  <!-- ... -->
</transition>
```
以上のように DOM イベントを読み込むために v-on ディレクティブを利用し、

```
// ...
methods: {
  // --------
  // ENTERING
  // --------

  beforeEnter: function (el) {
    // ...
  },

  enter: function (el, done) {
    // ...
    done()
  },
  afterEnter: function (el) {
    // ...
  },
  enterCancelled: function (el) {
    // ...
  },

  // --------

  beforeLeave: function (el) {
    // ...
  },
  leave: function (el, done) {
    // ...
    done()
  },
  afterLeave: function (el) {
    // ...
  },
  // v-show と共に使うときだけ leaveCancelled は有効です
  leaveCancelled: function (el) {
    // ...
  }
}
```
以上のように、メソッドをtransition内でDOM操作があった際のイベントにバインドできます。

## 実装
今回は２つ目の方法で実装していきます。
クリックイベントでstateが変更するようにし、


```
<div class="news-area" v-on:click="show = !show">
// ...
</div>
```

のようにクリックするとshow がtrue に変更されるようにします。


```
                        <transition
                            v-on:before-enter="beforeEnter"
                            v-on:enter="enter"
                            v-on:after-enter="afterEnter"
                            v-on:enter-cancelled="enterCancelled"
                            v-on:leave="leave"
                            v-bind:css="false"
                        >
                            <div class="babble" v-if="show">
                                <span id="babble1">●</span>
                                <span id="babble2">●</span>
                                <span id="babble3">●</span>
                            </div>
                        </transition>
```
vue の template内に上のように書きます。
今回は、３つの泡を同時に浮かばせて、もう一度クリックすると、３つの泡が別々の方向に飛んでいくような仕様にしました！
よってCSS は、`position:absolute` をつけて泡が初めは重なるようにしました。

```
.babble{
    position: absolute;
    color: #fff;
    span {
        position: absolute;
    }
}
```

javascript で、

```
 export default {
  data: function() {
    return {
      show: false
    };
  },

  methods: {
    beforeEnter: (el) => {
      el.style.opacity = 0
      el.style.left = event.pageX +'px'
    },
    enter: (el, done) => {
        Velocity(el, { opacity: 1, fontSize: '0.9em',translateX:'8px', translateY:'-90px' }, { duration: 1000,  easing: 'ease-in' })
        Velocity(el, {  translateX: '-8px;', translateY:'-190px' }, { duration: 700,  easing: 'linear' })
        Velocity(el, {  translateX: '8px', translateY:'-290px' }, { duration: 1000,  easing: 'ease-out' })
        Velocity(el, { fontSize: '1em' }, { complete: done })
    },
    enterCancelled: (el) => {
        Velocity(el.firstElementChild, {  opacity: 0, translateX: '-8px;', translateY:'-190px', fontSize: '0.3em'  }, { duration: 700,  easing: 'swing' })
        Velocity(el.children[1], {  opacity: 0, translateX: '-98px;', translateY:'190px', fontSize: '0.3em'  }, { duration: 700,  easing: 'swing' })
        Velocity(el.lastElementChild, {  opacity: 0, translateX: '80px;', translateY:'20px', fontSize: '0.3em'  }, { duration: 700,  easing: 'swing' })
    },
    afterEnter: (el) => {
        Velocity(el, { fontSize: '0.5em' }, { duration: 800, loop: 3 })
        Velocity(el, { opacity: 0.8}, { duration: 800,  easing: 'swing'})
    },
    leave: (el, done) => {
        Velocity(el.firstElementChild, {  opacity: 0, translateX: '-8px;', translateY:'-19px', fontSize: '0.3em'  }, { duration: 700,  easing: 'swing' })
        Velocity(el.children[1], {  opacity: 0, translateX: '-198px;', translateY:'190px', fontSize: '0.3em'  }, { duration: 700,  easing: 'swing' })
        Velocity(el.lastElementChild, {  opacity: 0, translateX: '80px;', translateY:'20px', fontSize: '0.3em'  }, { duration: 700,  easing: 'swing' , complete:done})
    }
  }
};
```
[Velocity.js](http://velocityjs.org/) のライブラリを使用し、アニメーションをつけました。
最終的には、マウスでクリックした位置の横軸を `event.pageX` で取得しその位置から泡が出るようにしました。また、クリックタイミングで自由に泡を破裂させることもできます。
こんな感じです↓


<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">vue で泡作った <a href="https://t.co/lZvmUMD1Nc">pic.twitter.com/lZvmUMD1Nc</a></p>&mdash; 山田健太郎/Yamada Kentaro (@reiwaStudent) <a href="https://twitter.com/reiwaStudent/status/1206201370044289025?ref_src=twsrc%5Etfw">December 15, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


後ろの波は HTML5 の canvas 要素で作りました。





## 終わりに
途中、俺何してるんだろうってなりました。
楽しかったです。