基礎からメモ: Vue.js CH6-p205 リストトランジション
リストの要素をグループ化して、追加、削除、移動のアニメーションを行う。
<transition-group>
タグを使いtag
属性でタグ名を指定する。tag
属性を省略するとspan
要素でラップされる。リストトランジションではキーの設定が必須。
<transition-group name="list" tag="ul"> <li v-for="item in list" v-bind:key="item.id"></li> </transition-group>
リストへの追加や削除、条件変更で描画状態が変化したときに、Enter
系とLeave
系のトランジションクラスが付与されるのは単一トランジションと同じ。リストトランジションではこれらに加えて、リストの順番が変わった時にMove
系のトランジションクラスとして.v-move
とが付与される。Move
はCSSのtransform
プロパティでシームレスな移動アニメーションを行う。
.v-move { transition: transform 1s; }
<transition-group>
に名前を付けてプレフィックスをv-
以外のものに変更できるのも単一トランジションと同じ。
移動トランジションの例
ボタンクリックでリストの順番を逆にする。
<div id="app"> <button v-on:click="order=!order">切り替え</button> <transition-group tag="ul" class="list" > <li v-for="item in sortedList" v-bind:key="item.id"> {{ item.name }} : {{ item.price }}円 </li> </transition-group> </div>
orderの値によってリスト順を反転する算出プロパティを定義
var app = new Vue({ el: '#app', data: { order: false, list: [ { id: 1, name: 'りんご', price: 100 }, { id: 2, name: 'ばなな', price: 200 }, { id: 3, name: 'いちご', price: 300 } ] }, computed: { // order値に応じてリストの順を反転する算出プロパティ sortedList: function() { // Lodash の orderBy を使用 return _.orderBy(this.list, 'price', this.order ? 'desc' : 'asc') } } });
.v-move { transition: transform 1s; }
ボタンを押すと順序がヌルっと反転する。
Leave と Move は同時発生することがある
例えば次のようなHTMLがある。リストアイテムは自身のshow
属性がtrue
の時だけ表示される。
<div id="app"> <button v-on:click="changeList">リストの1だけにする</button> <transition-group tag="ul" class="list" > <li v-for="item in list" v-if="item.show" v-bind:key="item.id"> {{ item.name }} : {{ item.price }}円 </li> </transition-group> </div>
ボタンをクリックするとメソッドが呼ばれ、リスト2と3のshow
属性が反転するので、消えたり表示されたりする。
var app = new Vue({ el: '#app', data: { list: [ { id: 1, name: 'りんご', price: 100, show:true }, { id: 2, name: 'ばなな', price: 200, show:true }, { id: 3, name: 'いちご', price: 300, show:true } ] }, methods: { // リストの2と3の表示を反転 changeList: function() { this.list[1].show = !(this.list[1].show); this.list[2].show = !(this.list[2].show); } } });
要素が最初に消えるアイテム2(ばなな)はLeave
系クラスだけが追加されるが、アイテム3(いちご)はアイテム2が消えた後に上へ移動してから消えるのでLeave
系とMove
系の両方のクラスが追加される。消える時のアイテム2と3に追加されるクラスはこんな感じになる。
<ul class="list"> <li>りんご : 100円</li> <li class="v-leave-active v-leave-to">ばなな : 200円</li> <li class="v-leave-active v-move v-leave-to">いちご : 300円</li> </ul>
今まで通りのCSSだと、アイテム3はtransition
プロパティが上書きされ、即座にopacity:0
となりゆっくりと移動して消える感じではなくなる。消える際にゆっくり消える2よりも先に3が消えるはずだ。
.v-enter-active, .v-leave-active { transition: opacity 1s, transform 1s; } .v-leave-active { position: absolute; } .v-move { transition: transform 1s; /* これが優先され、 v-leave-activeに指定したtransitionは上書きされる */ } .v-enter, .v-leave-to { opacity: 0; }
そういう場合は次のようなスタイルの一括指定か
.v-enter-active, .v-leave-active, .v-move { transition: opacity 1s, transform 1s; }
または、:not()
を利用することでうまくいく。
.v-move:not(.v-leave-active) { transition: transform 1s; }
inline-flex
なリストでの適用例(サポートサイトに載っている例)
Lodash
を使用
<div id="app"> <p> <button v-on:click="doShuffle">シャッフル</button> <button v-on:click="doAdd">追加</button> </p> <transition-group tag="ul" class="list"> <li v-for="(item, index) in list" v-bind:key="item" class="item" v-on:click="doRemove(index)"> {{ item }} </li> </transition-group> </div>
// 移動トランジション X&Y座標 var app = new Vue({ el: '#app', data: { list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, methods: { doShuffle: function () { this.list = _.shuffle(this.list) }, doAdd: function() { var newNumber = Math.max.apply(null, this.list) + 1 var index = Math.floor(Math.random() * (this.list.length + 1)) this.list.splice(index, 0, newNumber) }, doRemove: function (index) { this.list.splice(index, 1) } } });
.list { width: 240px; padding: 0; } .item { display: inline-flex; justify-content: center; align-items: center; margin: 4px; width: 40px; height: 40px; background: #f5f5f5; } /* トランジション用スタイル */ .v-enter-active, .v-leave-active, .v-move { transition: all 1s; } .v-leave-active { position: absolute; } .v-enter, .v-leave-to { opacity: 0; background: #f9a3b1; transform: translateY(-30px); }