CSS
JavaScript
animation
vue.js

《JavaScript》Vue.js で今日からはじめるアニメーション。

Vue.js では、要素を追加/更新/削除したときのアニメーションを、簡単に実装できるような機能が用意されています。

ありがたいことに、公式のドキュメントにバッチリとまとまっていますが、それを要約しつつパパッと今日から始められるようにと、ライトに端折って紹介します。

シンプルな実装

Vue が提供しているアニメーション機能は、かなり多機能で美味しい仕様になっていますが、とりあえず一番簡単なアプローチで実装してみます。

ポイント

  1. <transition name="your-anim-name"> で、v-ifv-show が指定された要素をラップする
  2. name 属性が class 名のプレフィクスになる
  3. 挿入(enter)/削除(leave)時のアニメーションの進捗を補足して、それに応じた class を自動で切り替える
  4. CSSの transitionanimation どちらも受け付ける

コード

<div id="app">
  <transition name="fade">
    <img v-show="show" class="image" src="https://source.unsplash.com/1600x900/?nature,water" alt="placeholder">
  </transition>
  <button class="button" id="button" @click="toggle">{{show ? 'leave' : 'enter'}}</button>
</div>
// enter の継続状態。トランジションがはじまるフェーズ中に適用。
// 要素が挿入される前に追加され、トランジション/アニメーションが終了すると削除
// 登場シーンの期間、遅延、イージングを定義
.fade-enter-active {
  transition: all 1s cubic-bezier(0.19, 1, 0.22, 1);
  // animation: fade .5s;
}


// leave の継続状態。トランジションが終わるフェーズ中に適用
// leave がトリガされるとき追加され、トランジション/アニメーションが終了すると削除
// 削除シーンの期間、遅延、イージングを定義
.fade-leave-active {
  transition: all 1s cubic-bezier(0.895, 0.03, 0.685, 0.22);
  // animation: fade .5s reverse;
}

//  enter の開始状態。
//  要素が挿入される前に適用され、要素が挿入された 1 フレーム後に削除
//  要素の登場シーンのスタイルがここ
.fade-enter {
  opacity: 0;
  transform: translateX(-100%);
}

// leave の終了状態
// トランジションの終了がトリガされた後に (同時に v-leave 削除) 1フレーム追加
// トランジション/アニメーションが終了すると削除
// 要素の削除シーンのスタイルがここ
.fade-leave-to {
  opacity: 0;
  transform: translateX(100%);
}
const root = new Vue({
   data: function() {
     return {
       show: false
     }
   },
   methods: {
    toggle() {
      this.show = !this.show
    }
  },
});
root.$mount( '#app' );

初期描画時に実行する

初期描画時に実行するには、 appear 属性を付与します。

<transition appear name="fade">
  <el></el>
</transition>

発展した使い方

よく使う、すこし高度な実装を紹介します。

いくつかの要素を入れ替えるアニメーション

表示要素を入れ替えるようなアニメーションには key 属性を利用します。

mode 属性でイベントが発火するタイミングをコントロールします。
削除する要素のアニメーションの完了を待ったあと、展開する要素のアニメーションを開始するように、 out-in を指定します。

<transition name="fade" mode="out-in">
  <button :key="show" class="button" id="button" @click="toggle">{{show ? 'leave' : 'enter'}}</button>
</transition>

JavaScriptのフック

class が自動切替えされるタイミングで、任意のコールバック関数を定義できます。これで、jQuery や TweenMax などのライブラリと連携をとって、より複雑なアニメーションを組み込めます。

CSSアニメーションを使わない場合は、:css="false" を記述し、enter(el, done) leave(el, done) の第二引数で、アニメーションの完了を教えてあげます。

<transition
    @enter="enter"
    @leave="leave"
    :css="false"
>
    <el></el>
</transition>
const root = new Vue({
    methods: {
      // 
      enter(el, done) {
        TweenMax.fromTo(el, 1, {
          x: '-100%',
          rotationX: 180,
        }, {
          x: '0%',
          rotationX: 0,
          scale: 1,
          onComplete: done,
          ease: Expo.easeOut,
        })
      },
      leave(el, done) {
        TweenMax.to(el, .5, {
          x: '100%',
          rotationX: 360,
          scale: .1,
          onComplete: done,
          ease: Expo.easeIn,
        })
      }
    },
  });

複数のアイテムのアニメーション

これまでは単一の要素を描画するためのアニメーションでしたが、複数の要素が追加/削除されるような場合のアニメーションを実装することもできます。

ポイント

  1. <transition-group> で要素をラップする
  2. key をそれぞれの要素に指定する
  3. .*-move で位置の変化を制御する

コード

<div id="app">
  <div class="field has-addons">
    <p class="control">
      <button class="button" @click="toggle">
        {{show ? 'leave' : 'enter'}}
      </button>
    </p>
    <p class="control">
      <button class="button" @click="add">
        add
      </button>
    </p>
    <p class="control">
      <button class="button" @click="shuffle">
        shuffle
      </button>
    </p>
  </div>
  <div class="field">
    <label class="label">Image Query</label>
    <div class="control">
      <input class="input" type="text" placeholder="Text input" v-model="query">
    </div>
    <p class="help is-info">Enter the keyword of the image you want to call.</p>
  </div>

  <transition-group name="fade" tag="ul" class="columns is-mobile is-multiline is-gapless">
    <li class="column is-one-third" v-show="show" v-for="item in items" :key="item.src">
      <img :src="item.src" alt="placeholder">
    </li>
  </transition-group>

</div>
let baseImageUrl = 'https://source.unsplash.com/1600x900/?';

const root = new Vue({
   data: function() {
     return {
       show: false,
       items: [
         {
           id: this.counter,
           src: baseImageUrl + 'nature,water'
         },
       ],
       query: '',
       counter: 0,
     }
   },
   methods: {
    toggle() {
      this.show = !this.show
    },
    add() {
      // 画像ロードするの待って
      let img = new Image();
      let src = baseImageUrl + this.query;
      img.onload = () => {
        this.items.push({
          src: src
        });
      };
      img.src = src;
    },
    shuffle() {
      this.items = _.shuffle(this.items)
    }
  }
});

root.$mount( '#app' );
// 位置の変化を検知する *-move 
.fade-move {
  transition: all 1s cubic-bezier(0.895, 0.03, 0.685, 0.22);
}

.fade-enter-active {
  // easeOutExpo
  transition: all 1s cubic-bezier(0.19, 1, 0.22, 1);
}

.fade-leave-active {
  // easeInQuart
  transition: all 1s cubic-bezier(0.895, 0.03, 0.685, 0.22);
}

.fade-enter {
  opacity: 0;
  transform: translateX(-100%);
}

.fade-leave-to {
  opacity: 0;
  transform: translateX(100%);
}

複数の要素の位置関係を把握して、要素の追加や並び替えに反応、適当な位置へアニメーションしてくれます。ソートUIのアニメーションを簡単に書くことができますね。

Vue.jsのアニメーションすごい

ざっくりとアニメーションまわりを紹介しましたが、Vue.jsのアニメーション機能は非常に実践的で整っている印象です。詳しくはドキュメントやサンプルを参照してください。

他にも機能を掘り下げていくといろんなことができそうなので、実験して模索していきたいですね。🍟

おわります。