Vue3
でスケルトンコンポーネントを作成し、非同期処理中にそれを表示させる方法を紹介します。
以下の環境で実装しています。また、Vue
はComposition API
を採用しています。
vite/3.2.5 linux-x64 node-v16.14.2
vue: 3.2.45
アニメーション付きのスケルトンコンポーネントを作成する
今回は大元となるスケルトンコンポーネントを自身で作成します。
以下のYouTube動画を参考に作成しています。
props
としてheight
, width
, borderRadius
を設定します。
型をstring
としているのは、100px
などの単位も含めて指定できるようにするためです。
v-bind
を利用して、props
から渡されたデータをCSSに適用しています。
<script setup lang="ts">
interface Props {
height?: string;
width?: string;
borderRadius?: string;
}
// デフォルト値の設定
const props = withDefaults(defineProps<Props>(), {
height: 'auto',
width: '100%',
borderRadius: '0%',
});
</script>
<template>
<div class="skeleton"> </div>
</template>
<style scoped>
.skeleton {
height: v-bind('props.height');
width: v-bind('props.width');
border-radius: v-bind('props.borderRadius');
}
</style>
このコンポーネント(AnimatedSkeleton
とします)を呼び出す際に、height
, width
, borderRadius
のいずれかを指定することで、呼び出し元に応じたスケルトンコンポーネントを作成できます。
<template>
<!-- 円のスケルトン -->
<AnimatedSkelton height="100px" width="100px" borderRadius="50%" />
<!-- 長方形のスケルトン -->
<AnimatedSkelton height="10px" width="300px" />
</template>
CSSアニメーションも加えたAnimatedSkeleton
の全体のソースは以下になります。
AnimatedSkeleton.vue
<script setup lang="ts">
// 呼び出し時にはheight, width, borderRadiusを指定(いずれも任意)
interface Props {
height?: string;
width?: string;
borderRadius?: string;
}
// デフォルト値の設定
const props = withDefaults(defineProps<Props>(), {
height: 'auto',
width: '100%',
borderRadius: '0%',
});
</script>
<template>
<div class="skeleton"> </div>
</template>
<style scoped>
// アニメーションの設定
@keyframes skeletonAnimation {
0% {
background-position: 50% 0;
}
100% {
background-position: -100% 0;
}
}
.skeleton {
height: v-bind('props.height');
width: v-bind('props.width');
border-radius: v-bind('props.borderRadius');
background-image: linear-gradient(to right, #d6d7d8 0%, #e2e3e4 10%, #d6d7d8 20%, #d6d7d8 100%);
background-size: 200% 100%;
animation: skeletonAnimation 1.2s linear infinite;
}
</style>
非同期処理実行中にスケルトンコンポーネントを表示する
今回はは、先述のAnimatedSkeleton
とQiita API v2を用いて、非同期処理が完了するまでの間に、スケルトンコンポーネントを表示する方法について解説します。
①v-if
を使う方法
シンプルな方法です。
まず、今回取得するデータにreactive
を使って初期値を設定します。
<script setup lang="ts">
import { reactive } from 'vue';
const profile = reactive({
profile_image_url: '',
id: '',
description: '',
});
</script>
以降はデータ取得処理を行い、取得が成功したらprofile
の各キーに取得データをセットします。
<template>
内では「データがあればこの要素を表示し、なければスケルトンコンポーネントを表示する」というのをv-if
を用いて以下のようにします。
<template>
<div>
<!-- profile.profile_image_urlがtrueならimgを表示 -->
<img
v-if="profile.profile_image_url"
:src="profile.profile_image_url"
alt=""
/>
<!-- profile.profile_image_urlがfalseならAnimatedSkeltonを表示 -->
<AnimatedSkelton v-else height="100px" width="100px" borderRadius="50%" />
</div>
</template>
UserData.vue
のソースコードと画面表示は以下から確認ができます。
②<Suspense>
を使う方法
もう一つ、<Suspense>
を使った方法を紹介します。
執筆時点で<Suspense>
は実験的な機能のため、今後動作や仕様などが変更される可能性もあります。利用時にはその可能性を留意してください。
なお、以下の説明も執筆時点のものであり、最新の仕様とは異なる場合があります。
v-if
の時は、UserData
コンポーネントの中の個々の要素に対してスケルトンコンポーネントを指定していました。今回は、UserData
のレイアウトに丸ごと対応させた、UserDataSkeleton
コンポーネントを作成します。
UserDataSkeleton.vue
<script setup lang="ts">
import AnimatedSkelton from '@/components/AnimatedSkeleton.vue';
</script>
<template>
<div class="container">
<div class="author_img">
<!-- UserDataの画像表示エリアに対応 -->
<AnimatedSkelton height="100px" width="100px" borderRadius="50%" />
</div>
<div class="description">
<div class="author_name">
<!-- UserDataのユーザー名表示エリアに対応 -->
<AnimatedSkelton height="2rem" width="8rem" />
</div>
<div>
<!-- UserDataの紹介文表示エリアに対応 -->
<AnimatedSkelton height="1rem" width="100%" />
<AnimatedSkelton height="1rem" width="100%" />
<AnimatedSkelton height="1rem" width="100%" />
</div>
</div>
</div>
</template>
<Suspense>
を利用する場合のコンポーネントとスケルトンコンポーネントの切り替えは、以下のように設定します。
<script setup lang="ts">
import UserData from '@/components/UserData.vue';
import UserDataSkeleton from '@/components/UserDataSkeleton.vue';
</script>
<template>
<main>
<Suspense>
<UserData />
<!-- 非同期処理完了まではスケルトンを表示 -->
<template #fallback>
<UserDataSkeleton />
</template>
</Suspense>
</main>
</template>
非同期コンポーネントであるUserData
コンポーネントがresolved
になるまで、つまり非同期処理が完了するまで、#fallback
で指定したUserDataSkeleton
(スケルトンコンポーネント)を表示します。
<Suspense>
を利用したソースコードの全容・画面表示は以下のリンクから確認ができます。
非同期処理中の制御を<Suspense>
が担ってくれるため、<Suspense>
はコンポーネントの中に複数の非同期コンポーネントが含まれる場合に特に有用です。
スケルトンコンポーネントの作成方法と、切り替え表示の方法について説明しました。
<Suspense>
を使う方がコードがシンプルになりますが、まだ実験的機能であることを考えると実践で投入するのは難しいかもしれません。今後も<Suspense>
の動向に着目していく必要があります