Vue アニメーション
Vue に組み込まれている <Transition> コンポーネントを使用すると、v-if や v-show、あるいはダイナミックコンポーネントによって要素が追加・削除される際にアニメーションを適用できます。
もちろん、それ以外のケースで素の CSS トランジションやアニメーションを使用することも全く問題ありません。
1. CSS トランジションとアニメーションの導入
このセクションでは、CSS のアニメーション(animation)とトランジション(transition)の基礎知識を前提としています。
Vue 特有の <Transition> コンポーネントを扱う前に、まずは Vue で素の CSS を使用する 2 つの例を見てみましょう。
1.1 基本的な CSS トランジション
以下の例では、v-bind を使用して <div> タグにクラスを付与し、回転させています。回転に 1 秒かかるのは、CSS の transition プロパティで定義されているためです。
App.vue:
<template>
<h1>基本的な CSS トランジション</h1>
<button @click="this.doesRotate = true">回転させる</button>
<div :class="{ rotate: doesRotate }"></div>
</template>
<script>
export default {
data() {
return {
doesRotate: false
}
}
}
</script>
<style scoped>
.rotate {
rotate: 160deg;
transition: rotate 1s;
}
div {
border: solid black 2px;
background-color: lightcoral;
width: 60px;
height: 60px;
}
h1, button, div {
margin: 10px;
}
</style>1.2 基本的な CSS アニメーション
次の例では、CSS の animation プロパティを使用してオブジェクトを移動させる方法を確認します。
App.vue:
<template>
<h1>基本的な CSS アニメーション</h1>
<button @click="this.doesMove = true">スタート</button>
<div :class="{ move: doesMove }"></div>
</template>
<script>
export default {
data() {
return {
doesMove: false
}
}
}
</script>
<style scoped>
.move {
animation: move .5s alternate 4 ease-in-out;
}
@keyframes move {
from {
translate: 0 0;
}
to {
translate: 70px 0;
}
}
div {
border: solid black 2px;
background-color: lightcoral;
border-radius: 50%;
width: 60px;
height: 60px;
}
h1, button, div {
margin: 10px;
}
</style>2. コンポーネント
素の CSS を使うのも良いですが、Vue の <Transition> コンポーネントを使えば、v-if や v-show で要素が削除されたり追加されたりするタイミングでのアニメーションを非常に簡単に実装できます。これは素の CSS だけでは実装が難しいケースです。
まずは、ボタンで <p> タグを追加・削除するだけのシンプルな例を作成します。
App.vue:
<template>
<h1>p タグの追加/削除</h1>
<button @click="this.exists = !this.exists">{{btnText}}</button><br>
<p v-if="exists">ハロー・ワールド!</p>
</template>
<script>
export default {
data() {
return {
exists: false
}
},
computed: {
btnText() {
if(this.exists) {
return '削除';
}
else {
return '追加';
}
}
}
}
</script>
<style>
p {
background-color: lightgreen;
display: inline-block;
padding: 10px;
}
</style>次に、この <p> タグを <Transition> で囲み、削除時のアニメーションを設定してみましょう。<Transition> を使用すると、要素の追加・削除時に 6 つの異なる CSS クラスが自動的に提供されます。
以下の例では、要素が削除される際にフェードアウトさせるため、自動生成される v-leave-from と v-leave-to クラスを使用しています。
App.vue:
<template>
<h1>p タグの追加/削除</h1>
<button @click="this.exists = !this.exists">{{btnText}}</button><br>
<Transition>
<p v-if="exists">ハロー・ワールド!</p>
</Transition>
</template>
<style>
.v-leave-from {
opacity: 1;
}
.v-leave-to {
opacity: 0;
}
p {
background-color: lightgreen;
display: inline-block;
padding: 10px;
transition: opacity 0.5s;
}
</style>3. 6 つの クラス
<Transition> コンポーネントを使用すると、以下の 6 つのクラスが利用可能になります。
要素が追加されるとき(Enter):
v-enter-from: 開始状態。v-enter-active: 実行状態。トランジションの期間やイージングを定義します。v-enter-to: 終了状態。
要素が削除されるとき(Leave):
v-leave-from: 開始状態。v-leave-active: 実行状態。v-leave-to: 終了状態。
注意:<Transition> コンポーネントのルートレベルには、要素を 1 つだけ 配置する必要があります。
3.1 Enter と Leave の両方をアニメーションさせる例
App.vue (style部分):
.v-enter-from {
opacity: 0;
translate: -100px 0;
}
.v-enter-to {
opacity: 1;
translate: 0 0;
}
.v-leave-from {
opacity: 1;
translate: 0 0;
}
.v-leave-to {
opacity: 0;
translate: 100px 0;
}
p {
background-color: lightgreen;
display: inline-block;
padding: 10px;
transition: all 0.5s;
}3.2 Active クラスでのアニメーション設定
v-enter-active や v-leave-active を使用して、追加中や削除中のスタイルやアニメーションを一括で設定することもできます。
App.vue (style部分):
.v-enter-active {
background-color: lightgreen;
animation: added 1s;
}
.v-leave-active {
background-color: lightcoral;
animation: added 1s reverse;
}
@keyframes added {
from {
opacity: 0;
translate: -100px 0;
}
to {
opacity: 1;
translate: 0 0;
}
}4. トランジションの 'name' プロップ
複数の <Transition> コンポーネントがあり、それぞれに異なるアニメーションを設定したい場合は、name プロップを使用して区別する必要があります。
name を指定すると、CSS クラスの接頭辞が v- から指定した名前に変わります。例えば <Transition name="swirl"> とした場合、クラス名は以下のようになります:
swirl-enter-fromswirl-enter-active...など。
4.1 実装例
App.vue:
<template>
<button @click="this.p1Exists = !this.p1Exists">追加/削除 1</button>
<Transition>
<p v-if="p1Exists">通常のトランジション</p>
</Transition>
<button @click="this.p2Exists = !this.p2Exists">追加/削除 2</button>
<Transition name="swirl">
<p v-if="p2Exists">スワール・アニメーション</p>
</Transition>
</template>
<style>
/* nameなし(v-)のスタイル */
.v-enter-active { animation: added 1s; }
/* name="swirl" のスタイル */
.swirl-enter-active { animation: swirlAdded 1s; }
@keyframes swirlAdded {
from { opacity: 0; rotate: 0; scale: 0.1; }
to { opacity: 1; rotate: 360deg; scale: 1; }
}
</style>5. JavaScript トランジションフック
各トランジションクラスは、JavaScript コードを実行できるイベント(フック)に対応しています。
| トランジションクラス | JavaScript イベント |
|---|---|
| v-enter-from | before-enter |
| v-enter-active | enter |
| v-enter-to | after-enter |
| - | enter-cancelled |
| v-leave-from | before-leave |
| v-leave-active | leave |
| v-leave-to | after-leave |
| - | leave-cancelled (v-showのみ) |
5.1 実装例
after-enter イベントをフックして、アニメーション完了後に別の要素を表示する例です。
App.vue:
<template>
<Transition @after-enter="onAfterEnter">
<p v-show="pVisible">ハロー・ワールド!</p>
</Transition>
<div v-show="divVisible">アニメーション完了後に表示されます。</div>
</template>
<script>
export default {
data() {
return { pVisible: false, divVisible: false }
},
methods: {
onAfterEnter() {
this.divVisible = true;
}
}
}
</script>6. 'appear' プロップ
ページ読み込み時に要素をアニメーションさせたい場合は、<Transition> に appear プロップを追加します。
App.vue:
<template>
<h1>appear プロップの動作</h1>
<Transition appear>
<p>ページを開いた瞬間にアニメーションします。</p>
</Transition>
</template>7. 要素間のトランジション
<v-if> と <v-else-if> を併用することで、複数の要素を切り替える際のトランジションも可能です。
App.vue:
<template>
<button @click="newImg">次へ</button>
<Transition>
<img src="/img_pizza.svg" v-if="imgActive === 'pizza'">
<img src="/img_apple.svg" v-else-if="imgActive === 'apple'">
</Transition>
</template>8. mode="out-in" の活用
デフォルトでは、新しい要素が追加されるのと同時に古い要素が削除されます。mode="out-in" プロップを使用すると、「古い要素が完全に消えてから、新しい要素が表示される」というスムーズな切り替えが実現できます。
App.vue:
<template>
<button @click="indexNbr++">画像を切り替える</button>
<Transition mode="out-in">
<img :src="currentImgSrc" :key="imgActive">
</Transition>
</template>9. ダイナミックコンポーネントとの併用
ダイナミックコンポーネントの切り替えにもアニメーションを適用できます。
App.vue:
<template>
<button @click="toggleValue = !toggleValue">コンポーネントを切り替える</button>
<Transition mode="out-in">
<component :is="activeComp"></component>
</Transition>
</template>
<script>
export default {
computed: {
activeComp() {
return this.toggleValue ? 'comp-one' : 'comp-two';
}
}
}
</script>