LoginSignup
6
4

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-12-15

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

概要

業務では、どちらかというと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操作があった際のイベントにバインドできます。

実装

今回は2つ目の方法で実装していきます。
クリックイベントで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内に上のように書きます。
今回は、3つの泡を同時に浮かばせて、もう一度クリックすると、3つの泡が別々の方向に飛んでいくような仕様にしました!
よって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 のライブラリを使用し、アニメーションをつけました。
最終的には、マウスでクリックした位置の横軸を event.pageX で取得しその位置から泡が出るようにしました。また、クリックタイミングで自由に泡を破裂させることもできます。
こんな感じです↓

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

終わりに

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

6
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4