はじめに
SPAの場合、非同期通信の間に何かしらのローディングを表示しないと
エンドユーザーはボタンがクリックできていないと思い、複数回クリックしてしまいます。
もちろん2度押し防止のボタンコンポーネントを使用することで多重リクエストを防ぐことはできますが、
画面遷移がある場合にはそのボタンは消えてしまいます。
画面遷移中にもローディングやプログレスバーなどは表示させてあげるべきだと考えています。
そこで今回はプログレスバーを実装してみます。
デモとコードも公開しています。ご参考までに。
実装
概要
画面遷移中にプログレスバーを表示することを考慮するため
表示非表示の切り替えのフラグはvuex
を使用し、store.state
で管理します。
アニメーションはcss
のkeyframes
を使用します。
ProgressBar.vue
プログレスバーコンポーネントを実装します。
<template>
<div class="progress-bar-bg" v-if="isProcessing">
<div class="progress-bar"></div>
</div>
</template>
<script>
export default {
computed: {
isProcessing() {
return this.$store.state.progressBar.processing;
}
}
};
</script>
template
プログレスバーの表示非表示はv-if
で切り替えます。
プログレスバーのアニメーションはcss
で行うため、class
を付与しています。
script
リアクティブにするため、computed
でstore
から表示非表示のフラグを返してあげます。
style
css
の keyframes
でプログレスバーのアニメーションを表現します。
$animation-time: 5s;
.progress-bar-bg {
width: 100%;
height: 3px;
background: #eee;
.progress-bar {
overflow: no-display;
position: absolute;
left: 0;
width: 0;
height: 3px;
background: #1496ed;
background-position: 100px 100px;
animation-name: moveIndeterminate;
animation-duration: $animation-time;
animation-timing-function: all $animation-time cubic-bezier(0.235, 0.285, 0.975, 0.055);
}
}
@keyframes moveIndeterminate {
0% {
width: 0;
}
10% {
width: 15%;
}
100% {
width: 100%;
}
}
$animation-time
$animation-time
はプログレスバーが0%
から100%
になるまでの時間を指定しています。
数字が大きくなるほどゆっくり進みます。
keyframes
keyframes
を0%
10%
100%
に設定しています。
(GitHub風アニメーション)
store/index.js
export default new Vuex.Store({
state: {
progressBar: {
processing: false
}
},
mutations: {
[START_PROCESS]({ progressBar }) {
progressBar.processing = true;
},
[END_PROCESS]({ progressBar }) {
const dom = document.querySelector(".progress-bar");
dom.style.animationPlayState = "paused";
dom.style.animation = "none";
dom.style.width = "100%";
setTimeout(() => {
progressBar.processing = false;
}, 300);
}
}
});
state
store.state.progressBar.processing
この値がtrue
の場合にプログレスバーを表示します。
mutations
START_PROCESS
START_PROCESS
が呼び出されるとprocessing
の値をtrue
に更新して、
プログレスバーのアニメーションを開始します。
END_PROCESS
END_PROCESS
が呼び出されるとprocessing
の値をfalse
に更新して、
プログレスバーのアニメーションを停止し非表示にします。
プログレスバーが最後まで到達せず、途中で非表示になると分かりにくいので
JS
でCSS
のアニメーションを停止して、さらにwidth: 100%;
にしてあげると良いでしょう。
dom.style.animationPlayState = "paused";
dom.style.animation = "none";
dom.style.width = "100%";
さらに、処理が終わったことをわかりやすくするためにsetTimeout
を使用します。
width: 100%;
にした後に300 [ms]
プログレスバーを表示した状態にしてから非表示にします。
setTimeout(() => {
progressBar.processing = false;
}, 300);
App.vue
今回はApp.vue
にプログレスバーコンポーネントを設置します。
そして、処理開始時と終了時のタイミングでstore
にcommit
します。
store
で状態を管理しているため、処理終了時も呼び出さなければいけません。
毎回呼び出さなければいけませんが、store
で管理することで、
どのコンポーネントやクラスからでも表示非表示の切り替えができます。
処理開始
store.commit(START_PROCESS);
処理終了
store.commit(END_PROCESS);
エラー発生時などにも例外処理などでプログレスバーを非表示にする処理を書いてあげないと、
プログレスバーが表示されたままになるため、注意が必要です。
おわりに
全体のソースはCodeSandbox で確認してください。
個人的にはUXの観点から画面全体を覆うローディングよりもプログレスバーが最適だと思っています。
また、プログレスバー表示時に画面全体を透明の背景で覆ってあげると、2度押し防止にもなります。
ボタンコンポーネントを用意して、クリックするとボタンがローダーに変わるのが一番自然な気はします。
しかし、CSRF対策などで画面をまたいでリクエストが発生する場合には、
画面遷移したタイミングでボタンが消えてしまって不自然だと感じます。
なので、画面上部のプログレスバーがオススメです。
以上