Vue.jsでプレースホルダを用いたローディングを実装する際のトランジション

この記事の実装例はこちらです。

背景

UX向上の観点により、読み込みが遅いもの(特に大きなイメージファイルなど)が存在する場合、先に枠を表示し、ダウンロードできた後本物に置き換える設計は最近増えてきています。そのうち、下の図で示されているようなFacebookのロード画面は好例の一つだと言えるでしょう。

Facebookローディング画面のプレスホルダー

この設計をVue.jsで実装するのはそれほど難しくないでしょう。いくつかのVue.js製のプレスホルダーはすでに公開されている(その一その二)ため、それらを条件付きレンダリング(Conditional Rendering)と合わせ、ディレクティブv-ifv-elseを活用するだけで実現可能です。

1
2
<placeholder v-if="content === null" />
<my-content v-else />

こういう風に実装してもダメとは言えませんが、コンテンツの切り替えにトランザクションが全く存在しないため、唐突の感は免れません。よって、置き換えが発生した際に、フェードイン/フェードアウト効果を追加した方がUX的に優しいと思われます。

トランジションモード

Vue.jsでトランジションを追加するのに、非常に簡単なやり方が存在します。そう、Transitionというラッパーコンポーネントで囲み、CSSで定義する方法です。

トランジションの状態定義

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<transition name="fade">
<placeholder v-if="content === null" />
<my-content v-else />
</transition>
</template>

<script>
// 省略
</script>

<style>
.fade-enter-active, .fade-leave-active {
transition: opacity 1s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
</style>

しかし、これだけでは変な動きが出てきます。一時的にプレスホルダーと本物が同時に画面上に現れるわけです。
理由は単純です。transition内の子コンポーネントのアニメーションが同時に開始するため、プレスホルダーのフェードアウトアニメーションが再生されている間に、本物のコンポーネントがすでに出てきてしまいました。
これを回避するために、Vue.jsがトランジションモードを用意してくれました。現時点ではout-inin-out2種類が提供されていて、out-inを利用すれば、コンポーネントAが消失した後、コンポーネントBを出現させることは可能になります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<transition name="fade" mode="out-in">
<placeholder v-if="content === null" />
<my-content v-else />
</transition>
</template>

<script>
// 省略
</script>

<style>
.fade-enter-active, .fade-leave-active {
transition: opacity 1s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
</style>

より良い表現を求めて

上記の解決策は悪くないと思いますが、もう少しいい表現ができると思いました。今の場合、トランジション効果は一つずつ再生されるため、所用時間が長くなり、入れ替わる感が感じにくくなっています。その為、やはりフェードアウトとフェードインは同時に行って欲しいですね。となると、トランジションモードの使用を取りやめ、その代わりに、CSSのpositionで二つのコンポーネントの位置を同じところに固定すれば、望んでいた効果が出せるはずです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<transition name="fade">
<placeholder v-if="content === null" class="my-content" />
<my-content v-else class="my-content" />
</transition>
</template>

<script>
// 省略
</script>

<style>
.fade-enter-active, .fade-leave-active {
transition: opacity 1s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
.my-content {
position: absolute;
}
</style>

しかし、見た目は完璧でしょうが、この方法はトランジションモードの方法より扱いにくいはずです。なぜかと言いますと、position: absolute;の使用によって、コンポーネントらがドキュメントフローから出てしまうので、CSS上で配慮しなければいけないものが増えてしまいます。

まとめ

もっといい方法があるのではないかと思いますが、今回はただの実験なので、これ以上追求しませんでした。一見簡単そうなテーマでしたが、完璧に実現することは意外と難しいということを感じましたね。