この記事は クラウドワークス Advent Calendar 2019 の3日目の記事です。
昨日は、@minamijoyo さんによるtfupdateでTerraform本体/プロバイダ/モジュールのバージョンアップを自動化する でした!
はじめに
こんにちは、最近アルコールに負け続けている新卒エンジニアの @d4te です。
Rubyの会社に入社して7ヶ月ほど経ったのですが、この7ヶ月間あまり Ruby は書かずに Vue.js を利用したフロントエンド開発ばかりしていました。
その中でも Vue.js でのアニメーションの実装は経験がなく、一から調べる機会があったので今回はそれについて書いていこうと思います。
基本的なこと
Vue.js はデフォルトでトランジション( transition
)という機能を提供していて、それを利用することでいい感じにアニメーションを実装することができます。
まずここにボタンを押したら div.hoge
を表示/非表示するだけの 単一ファイルコンポーネント があります。いい感じのアニメーションはありません。
<template>
<div id="app">
<button @click="show = !show">
click!
</button>
<!-- アニメーションで表示/非表示が切り替わる時にふわっと表示させたい要素 -->
<div class="hoge" v-if="show">
hogehoge
</div>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
show: false
}
}
}
</script>
See the Pen VwwJmBq by Date (@b0ntenmaru) on CodePen.
いい感じのアニメーションを追加する
アニメーションを動作させるためには該当の要素(ここでは div.hoge
)を <transition>
タグで囲み、トランジションクラスに対して CSS を指定します。トランジションクラスに関しては後述します。
<transition>
<div class="hoge" v-if="show">
hogehoge
</div>
</transition>
<style>
/* 以下の v-enter, v-enter-to, v-enter-active がトランジションクラス */
/* 表示アニメーションをする前のスタイル */
.v-enter {
opacity: 0;
}
/* 表示アニメーション後のスタイル */
.v-enter-to {
opacity: 1;
}
/* 表示アニメーション動作中のスタイル */
.v-enter-active {
transition: all 500ms;
}
</style>
これにより、要素の表示に動きを与えることができました。
See the Pen dyyxrJr by Date (@b0ntenmaru) on CodePen.
トランジションクラス
トランジションクラスは、 <transition>
タグで要素を囲むことで使用できるようになるCSSのクラスです。
ちょうど上で登場した v-enter
, v-enter-to
, v-enter-active
がトランジションクラスにあたります。
トランジションクラスには Enter と Leave の2つのフェーズが存在し、
-
<transition>
タグで囲った要素を表示する時を Enter -
<transition>
タグで囲った要素を非表示にする時を Leave
と言います。(上のサンプルでは Enter のみ)
この2つのフェーズにはそれぞれ、アニメーションの動作前・動作中・動作後の3つ状態に対応した3つのクラスがあります。
クラス | 要素の状態 |
---|---|
v-enter | 表示アニメーションの開始時のスタイル |
v-enter-to | 表示アニメーションの終了時のスタイル |
v-enter-active | 表示アニメーション中のスタイル |
v-leave | 非表示アニメーションの開始時のスタイル |
v-leave-to | 非表示アニメーションの終了時のスタイル |
v-leave-active | 非表示アニメーション中のスタイル |
いい感じの非表示アニメーションを追加する
続いて非表示アニメーションを実装します。
Enter の時とやることは逆ですが、下記のように指定すると非表示アニメーションが動いてくれます。
/* 非表示アニメーション動作前のスタイル */
.v-leave {
opacity: 1;
}
/* 非表示アニメーション動作後のスタイル */
.v-leave-to {
opacity: 0;
}
/* 非表示アニメーション動作中のスタイル */
.v-leave-active {
transition: all 500ms;
}
See the Pen yLyBLRP by Date (@b0ntenmaru) on CodePen.
このトランジションクラスが Vue でアニメーションを実装するための基本となります。
複数要素のトランジション
続いて、v-for
など同時に描画された複数の要素にアニメーションを適応させるためのリストトランジションについて説明します。
下記の単一ファイルコンポーネントを例とします。
現状 hoge
が一覧されており、ADD ボタン押下で新しい hoge
が一覧に追加・ hoge
横の x ボタン押下で一覧から削除できる仕様となっています。
<template>
<div id="app">
<button @click="add">ADD</button>
<div v-for="(todo, index) in todos" :key="todo.key">
<span>{{ todo.value }}</span><input @click="remove(index)" type="button" value="x" />
</div>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
todos: [
{key: 0, value: 'hoge'},
{key: 1, value: 'hoge'},
{key: 2, value: 'hoge'},
],
nextNum: 2
}
},
methods: {
add: function() {
const value = 'hoge'
const todo = {
key: this.nextNum += 1,
value
}
this.todos.push(todo)
},
remove: function(index) {
this.todos.splice(index, 1)
}
}
}
</script>
See the Pen LYEPxyJ by Date (@b0ntenmaru) on CodePen.
いい感じのアニメーションを適応させる
hoge
追加時/削除時にアニメーションを適応させます。
やることとしては単一要素のトランジションと同じでですが、今回のような複数の要素にアニメーションを適応させる時は <transition-group>
タグで囲い、フェーズ( Enter / Leave )ごとにスタイルを書いていきます。
<transition-group>
<div v-for="(todo, index) in todos" :key="todo.key">
<span>{{ todo.value }}</span><input @click="remove(index)" type="button" value="x" />
</div>
</transition-group>
<style>
/* 表示・非表示アニメーション中 */
.v-enter-active, .v-leave-active {
transition: all 500ms;
}
/* 表示アニメーション開始時 ・ 非表示アニメーション後 */
.v-enter, .v-leave-to {
opacity: 0;
}
</style>
See the Pen VwYZPNo by Date (@b0ntenmaru) on CodePen.
一点自分がハマったポイントがあるのでこちらも是非読んでみてください。
これで複数要素のアニメーションが実装できました。
ですが削除ボタン押下後、要素が移動する時にアニメーションがなく、新しい位置に味気なく移動してしまっています。
要素移動のトランジション
<transition-group>
は Enter / Leave だけでなく、要素の移動のためのトランジションクラスである v-move
クラスを使うことで上のような味気ない移動にアニメーションを加えることができます。
やることとしては、簡単で以下の2点を追記します。
/* 要素が移動する時に700msで移動するように指定 */
.v-move {
transition: all 700ms;
}
.v-leave-active {
/* 移動のトランジションをさせる場合は非表示アニメーション中に position: absoluteを指定しないと正しく動作しない */
position: absolute;
}
See the Pen jOENGvN by Date (@b0ntenmaru) on CodePen.
これで複数要素の表示/非表示/移動時のアニメーションが実装できました。
ちなみにこの v-move
に関しては要素のあらゆる移動の時に適応されるので、複数要素をシャッフルさせる機能を追加してもいい感じに動いてくれます。
See the Pen yLyBxZQ by Date (@b0ntenmaru) on CodePen.
ハマったポイント
配列のインデックスをキーにv-bindしてはダメ
中の要素は、key 属性を持つことが 必須 です。
と Vue の公式にも記述されているように、 <transition-group>
タグ内部の v-for で指定された要素はそれぞれが key を持つことが必須ですが、ここに配列(ここでは todos
)の index を渡してはいけません、アニメーションが正常に動作しなくなります。
下記はkeyに配列の index を渡した例です。
削除ボタンを押すと決まって最後の要素が削除されたように見えてしまっています。
<transition-group>
<div v-for="(todo, index) in todos" :key="index">
<span>{{ todo.value }}</span><input @click="remove(index)" type="button" value="x" />
</div>
</transition-group>
See the Pen GRgKXaE by Date (@b0ntenmaru) on CodePen.
Vue.js の key はどの値に変更があったのかを追うために使われていて、 index を key に指定した場合、最後より前の要素を消すことによって index の値が更新され、 Vue がどの要素をアニメーションさせれば良いかわからなくなり、一番最後の要素が消えるような挙動となってしまうようです。なので key には配列の index ではない一意な値を渡してあげましょう。
終わりに
以上、「Vue.jsでいい感じのアニメーションを作りたい」でした。
アニメーションがいい感じかどうかはさておき、 Vue.js でも簡単にアニメーションを実装できることがお分りいただけたかと思います。
Vue.js のアニメーションには状態のトランジションなどもあるので是非いろいろ触ってみてください!