Skeleton Loader
This document provides a comprehensive guide to implement a Skeleton Loader component used as a placeholder while data is being fetched in another component.
These skeleton loaders will only apply to the first blocks or blocks that are visible when loading the data.
Each skeleton loader must have the same html and style structure as the component on which it acts.
SkeletonLoader.vue
This component serves as a skeleton loader with a specific design for the title and subtitle elements.
Template
<script setup lang="ts"></script><template> <div class="skeleton__data"> <div class="skeleton__effect"></div> <div class="skeleton skeleton__title"> <div class="skeleton__effect"></div> </div> <div class="skeleton skeleton__subtitle"> <div class="skeleton__effect"></div> </div> </div></template>Styles
<style scoped lang="scss">.skeleton { background-color: var(--c-graphite); filter: brightness(0.9); border-radius: 0.25rem; position: relative; overflow: hidden; margin: map.get($sizes, 's-margin', 'top-banner');
&__data { filter: brightness(0.9); border-radius: 0.25rem; width: 100%; height: 40em; @include flex(column, $align_items: flex-start); padding: map.get($sizes, 's-padding', 'general'); }
&__title { width: 44rem; height: 7em; margin: 1em 0; }
&__subtitle { width: 25.25em; height: 5em; margin: 0.5em 0; }
&__effect { @include skeleton; }}</style>Description
- skeleton__data: Wrapper for the entire skeleton loader.
- skeleton__effect: Common effect applied to all skeleton elements.
- skeleton__title: Skeleton placeholder for the title.
- skeleton__subtitle: Skeleton placeholder for the subtitle.
Usage in Another Component
Below is an example of how to use the Skeleton Loader component within another component that fetches and displays data.
HeroComponent.vue
This component displays a hero block with a title, subtitle, and a video. If the data is still loading, it shows the HeroSkeleton loader.
Template
<script setup lang="ts">import type { HeroBlock } from '@/interfaces/api/home'import { getRandomInt } from '@/utils/randomNumber'const HeroSkeleton = defineAsyncComponent( () => import('@/components/skeleton-loader/HeroSkeleton.vue'),)
interface Props { heroBlock: HeroBlock | undefined pending: boolean}const props = defineProps<Props>()
const randomIndex = computed(() => getRandomInt(props.heroBlock?.video.length ?? 0))</script>
<template> <article class="hero"> <HeroSkeleton v-if="$props.pending" /> <template v-else> <article class="hero__data"> <div class="hero__div"> <h2 class="hero__title">{{ $props.heroBlock?.title }}</h2> <h3 class="hero__subtitle">{{ $props.heroBlock?.subtitle }}</h3> </div> <video class="hero__video" autoplay controlslist="nodownload" loop muted :src="$props.heroBlock?.video[randomIndex]?.url" ></video> </article> </template> </article></template>Styles
<style scoped lang="scss">.hero { @include flex(column, flex-start); margin: map.get($sizes, 's-margin', 'bottom-hero');
&__data { filter: brightness(0.9); border-radius: 0.25rem; width: 100%; height: 40em; @include flex($justify_content: flex-start); }
&__div { position: absolute; z-index: 2; padding: map.get($sizes, 's-padding', 'general'); }
&__title { width: 44rem; text-wrap: wrap; @include font-style(map.get($fonts, f-font-semiBold), 3em); }
&__subtitle { width: 13em; margin: 1em 0; text-wrap: wrap; @include font-style(map.get($fonts, f-font-book), 2em); }
&__video { position: relative; top: 0; width: 100%; height: 100%; filter: brightness(0.8); }}</style>Description
- hero: Main wrapper for the hero component.
- hero__data: Wrapper for the data content including title, subtitle, and video.
- hero__div: Container for title and subtitle.
- hero__title: Displays the title of the hero block.
- hero__subtitle: Displays the subtitle of the hero block.
- hero__video: Displays the video associated with the hero block.
Props
- heroBlock: Contains the data for the hero block including title, subtitle, and video URLs.
- pending: Boolean indicating whether the data is still being fetched.
Computed Properties
- randomIndex: Computes a random index to select a video from the hero block’s video array.
- This function is in the path
utils/randomNumber.ts
Example Usage
To use the HeroComponent, import it and provide the necessary props:
<template> <HeroComponent :heroBlock="heroData" :pending="isLoading" /></template>
<script setup lang="ts">import HeroComponent from '@/components/HeroComponent.vue'import { ref } from 'vue'
const heroData = ref({ title: "Hero Title", subtitle: "Hero Subtitle", video: [{ url: "video1.mp4" }, { url: "video2.mp4" }]})
const isLoading = ref(true)
// Simulate data fetchingsetTimeout(() => { isLoading.value = false}, 2000)</script>This example simulates data fetching by setting isLoading to false after 2 seconds, at which point the HeroSkeleton loader will be replaced by the actual hero block content.