LoginSignup
0
0

画面の読み込みをスタイリッシュに待つ方法

Posted at

概要

  • 表示に時間のかかる情報を扱う際、ユーザーを待たせてしまうことがある
  • Qiitaの関連ページの読み込み表示は、読み込み中であることを視覚的に伝えながら、ユーザーの待ち時間を許容させる効果がある
  • 同様の読み込み表示を自分のアプリケーションに実装したい

前提条件

  • Vue.jsの基本的な知識があること
  • SCSSを使用できる環境が整っていること

cssでもできるが、scssを学習中であるためscssで実装している
必要であれば、scss->cssに直して作成してほしい

SCSSでのスタイリング

読み込み中のスケルトンカードのスタイル定義

.loading-card {
  background-color: #f5f5f5; // 背景色の設定
  border-radius: 8px; // 角丸の設定
  padding: 16px; // 内側の余白設定
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); // 影の設定
  animation: loading-animation 1.5s infinite; // アニメーションの適用
  height: 150px; // 高さの設定
}

アニメーションの定義

@keyframes loading-animation {
  0% {
    background-color: #f5f5f5; // 開始時の背景色
  }
  50% {
    background-color: #e0e0e0; // 中間地点の背景色
  }
  100% {
    background-color: #f5f5f5; // 終了時の背景色
  }
}
  • @keyframesを使ってアニメーションを定義
  • 背景色を変化させることで、読み込み中であることを表現

レスポンシブデザインの適用

.example-list {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20px;

  &-container {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); // レスポンシブなカラム設定
    grid-gap: 20px; // カード間の間隔設定
    width: 100%; // 幅の設定
  }
}

.loading-container {
  @extend .example-list-container;
}
  • grid-template-columnsrepeat()関数を使ってレスポンシブなカラム設定
  • grid-gapでカード間の間隔を設定
  • .loading-container.example-list-containerと同じスタイルを継承

Vue.jsでの実装

テンプレートの準備

<template>
  <div class="example-list">
    <div v-if="loading" class="loading-container">
      <div v-for="i in 20" :key="i" class="loading-card"></div>
    </div>
    <div v-else-if="examples.length > 0" class="example-list-container">
      <!-- 実際のデータの表示 -->
    </div>
    <div v-else class="no-examples">No examples found.</div>
  </div>
</template>
  • v-ifディレクティブを使って、loadingの状態に応じて表示を切り替える
  • v-forディレクティブを使って、20個のスケルトンカードを表示

APIの代替処理

onMounted(async () => {
  try {
    // 3秒待機してからデータを取得する
    await new Promise((resolve) => setTimeout(resolve, 3000))

    // ランダムでエラーを発生させる
    if (Math.random() < 0.5) {
      throw new Error('Failed to fetch examples')
    }

    // ダミーデータを設定する
    examples.value = [
      { id: 1, title: 'Example 1', description: 'This is example 1' },
      { id: 2, title: 'Example 2', description: 'This is example 2' },
      { id: 3, title: 'Example 3', description: 'This is example 3' },
    ]
  } catch (error) {
    console.error('Failed to fetch examples:', error)
  } finally {
    loading.value = false
  }
})
  • onMountedライフサイクルフックで、コンポーネントのマウント後に実行
  • 3秒待機してからデータの取得をシミュレート
  • ランダムでエラーを発生させる
  • ダミーデータを設定する
  • finallyブロックで、loading状態をfalseに設定し、読み込み完了を示す

全体のコード

<template>
  <div class="example-list">
    <div v-if="loading" class="loading-container">
      <div v-for="i in 20" :key="i" class="loading-card"></div>
    </div>
    <div v-else-if="examples.length > 0" class="example-list-container">
      <div
        v-for="example in examples"
        :key="example.id"
        class="example-card"
      >
        <h3 class="example-title">{{ example.title }}</h3>
        <p class="example-description">説明: {{ example.description }}</p>
      </div>
    </div>
    <div v-else class="no-examples">No examples found.</div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'

const examples = ref([])
const loading = ref(true)

onMounted(async () => {
  try {
    await new Promise((resolve) => setTimeout(resolve, 3000))

    if (Math.random() < 0.5) {
      throw new Error('Failed to fetch examples')
    }

    examples.value = [
      { id: 1, title: 'Example 1', description: 'This is example 1' },
      { id: 2, title: 'Example 2', description: 'This is example 2' },
      { id: 3, title: 'Example 3', description: 'This is example 3' },
    ]
  } catch (error) {
    console.error('Failed to fetch examples:', error)
  } finally {
    loading.value = false
  }
})
</script>

<style lang="scss" scoped>
.example-list {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20px;

  &-container {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    grid-gap: 20px;
    width: 100%;
  }
}

.loading-container {
  @extend .example-list-container;
}

.loading-card {
  background-color: #f5f5f5;
  border-radius: 8px;
  padding: 16px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  animation: loading-animation 1.5s infinite;
  height: 150px;

  @keyframes loading-animation {
    0% {
      background-color: #f5f5f5;
    }
    50% {
      background-color: #e0e0e0;
    }
    100% {
      background-color: #f5f5f5;
    }
  }
}

.example-card {
  background-color: #f5f5f5;
  border-radius: 8px;
  padding: 16px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);

  .example-title {
    margin-bottom: 8px;
  }
}

.no-examples {
  font-size: 18px;
  font-weight: bold;
}
</style>

まとめ

今回は簡単に点滅するだけのアニメーションを追加した。個人的には満足できるが、Qiitaのものはもっと動きがあり面白く、実装は複雑になりそうだとも感じた。しかし、この程度の簡単なアニメーションであっても簡単にLoading...と出したり、スピナーを回したりするよりも待たされている感は低減するため、多用はできないがどうしても重い処理の場合などは実装するとユーザー体験が大幅に向上しそうであると感じた。

0
0
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
0
0