25
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【Vue.js】画面上部にプログレスバーを表示する

Last updated at Posted at 2019-02-04

:point_up: はじめに

SPAの場合、非同期通信の間に何かしらのローディングを表示しないと
エンドユーザーはボタンがクリックできていないと思い、複数回クリックしてしまいます。
もちろん2度押し防止のボタンコンポーネントを使用することで多重リクエストを防ぐことはできますが、
画面遷移がある場合にはそのボタンは消えてしまいます。
画面遷移中にもローディングやプログレスバーなどは表示させてあげるべきだと考えています。
そこで今回はプログレスバーを実装してみます。

progressbar.gif

デモとコードも公開しています。ご参考までに。

CodeSandbox

:keyboard: 実装

概要

画面遷移中にプログレスバーを表示することを考慮するため
表示非表示の切り替えのフラグはvuexを使用し、store.stateで管理します。
アニメーションはcsskeyframesを使用します。

ProgressBar.vue

プログレスバーコンポーネントを実装します。

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

リアクティブにするため、computedstoreから表示非表示のフラグを返してあげます。

style

csskeyframes でプログレスバーのアニメーションを表現します。

$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

keyframes0% 10% 100% に設定しています。
(GitHub風アニメーション)

store/index.js

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 に更新して、
プログレスバーのアニメーションを停止し非表示にします。

プログレスバーが最後まで到達せず、途中で非表示になると分かりにくいので
JSCSS のアニメーションを停止して、さらに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 にプログレスバーコンポーネントを設置します。
そして、処理開始時と終了時のタイミングでstorecommit します。

store で状態を管理しているため、処理終了時も呼び出さなければいけません。
毎回呼び出さなければいけませんが、store で管理することで、
どのコンポーネントやクラスからでも表示非表示の切り替えができます。

処理開始

store.commit(START_PROCESS);

処理終了

store.commit(END_PROCESS);

エラー発生時などにも例外処理などでプログレスバーを非表示にする処理を書いてあげないと、
プログレスバーが表示されたままになるため、注意が必要です。

:pray: おわりに

全体のソースはCodeSandbox で確認してください。

個人的にはUXの観点から画面全体を覆うローディングよりもプログレスバーが最適だと思っています。
また、プログレスバー表示時に画面全体を透明の背景で覆ってあげると、2度押し防止にもなります。

ボタンコンポーネントを用意して、クリックするとボタンがローダーに変わるのが一番自然な気はします。
しかし、CSRF対策などで画面をまたいでリクエストが発生する場合には、
画面遷移したタイミングでボタンが消えてしまって不自然だと感じます。
なので、画面上部のプログレスバーがオススメです。

以上

25
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?