1
0
Fork 0

Add loading overlay

This commit is contained in:
Moritz Ruth 2021-12-18 17:49:09 +01:00
parent 8ec13b18f0
commit 481e30c1b3
No known key found for this signature in database
GPG key ID: AFD57E23E753841B
6 changed files with 156 additions and 15 deletions

View file

@ -19,9 +19,6 @@
"windicss": "^3.2.1"
},
"dependencies": {
"@fontsource/fanwood-text": "^4.5.0",
"@fontsource/league-gothic": "^4.5.0",
"@fontsource/linden-hill": "^4.5.0",
"@fontsource/plus-jakarta-sans": "^4.5.0",
"@fontsource/syne": "^4.5.0",
"@iconify/json": "1.1.444",

View file

@ -1,10 +1,27 @@
<template>
<div class="h-100vh w-100vw text-light-100 overflow-x-hidden">
<router-view v-slot="{ Component }">
<suspense>
<component :is="Component"/>
</suspense>
</router-view>
<suspense @pending="startLoading()" @resolve="stopLoading()">
<router-view v-slot="{ Component }">
<suspense @pending="startLoading()" @resolve="stopLoading()">
<component :is="Component"/>
</suspense>
</router-view>
</suspense>
</div>
<div class="bg-background fixed top-0 left-0 right-0 bottom-0 z-100 backdrop-filter" :style="loadingOverlayStyle">
<div class="font-bold text-light-900 text-2xl h-full w-full flex justify-center items-center overflow-hidden" :style="loadingOverlayContentStyle">
<div>
Loading...
</div>
<div
v-for="loadingText in loadingTexts"
:key="loadingText.x + '|' + loadingText.y"
:style="{ top: loadingText.x + 'px', left: loadingText.y + 'px' }"
:class="$style.loadingText"
>
Loading...
</div>
</div>
</div>
</template>
@ -16,10 +33,116 @@
min-height: 100vh;
font-size: 17px;
}
.loadingText {
animation: fade-in-out 8s ease both;
position: absolute;
}
@keyframes fade-in-out {
0%, 100% {
opacity: 0;
}
50% {
opacity: 1;
}
}
</style>
<script>
<script lang="ts">
import { computed, ref } from "vue"
import { useWindowSize, whenever } from "@vueuse/core"
import { pageComponentLoading } from "./store"
export default {
name: "App"
name: "App",
setup() {
const loadingStartedTime = ref<null | number>(null)
const isLoadingScreenActive = ref(false)
const START_TRANSITION_DURATION = 300
const START_TRANSITION_DELAY = 200
let suspenseLoading = false
const loadingTexts = ref<Array<{ x: number, y: number }>>([])
const stopLoading = () => {
if (loadingStartedTime.value === null) return
const stop = () => {
isLoadingScreenActive.value = false
loadingStartedTime.value = null
suspenseLoading = false
}
const timeSinceStart = Date.now() - loadingStartedTime.value
if (timeSinceStart > START_TRANSITION_DELAY) setTimeout(stop, START_TRANSITION_DURATION - timeSinceStart)
else stop()
}
const startLoading = () => {
if (loadingStartedTime.value !== null) return
loadingStartedTime.value = Date.now()
isLoadingScreenActive.value = true
loadingTexts.value = []
}
// Suspense @resolve is also called when the component is not async, so we don't need to handle stopping
whenever(pageComponentLoading, () => {
if (loadingStartedTime.value !== null) return
loadingStartedTime.value = Date.now()
isLoadingScreenActive.value = true
})
const { width, height } = useWindowSize()
setInterval(() => {
const start = loadingStartedTime.value
if (start === null) return
if (Date.now() - start > 3000) {
if (loadingTexts.value.length > 500) loadingTexts.value = []
loadingTexts.value = [...loadingTexts.value, { x: Math.random() * width.value, y: Math.random() * height.value }]
}
}, 800)
return {
isLoadingScreenActive,
startLoading,
stopLoading,
loadingTexts,
loadingOverlayStyle: computed(() => {
if (isLoadingScreenActive.value) {
return {
"--tw-bg-opacity": 1,
"--tw-backdrop-blur": "blur(20px)",
transitionDelay: `${START_TRANSITION_DELAY}ms`,
transition: `background ${START_TRANSITION_DURATION}ms ease, backdrop-filter 100ms linear`
}
} else {
return {
"--tw-bg-opacity": 0,
"--tw-backdrop-blur": 0,
transition: "background 500ms ease, backdrop-filter 200ms linear",
pointerEvents: "none"
}
}
}),
loadingOverlayContentStyle: computed(() => {
if (isLoadingScreenActive.value) {
return {
opacity: 1,
transition: `opacity 2s ease-out`,
transitionDelay: "1s"
}
} else {
return {
opacity: 0,
transition: `opacity 100ms ease-out`,
pointerEvents: "none"
}
}
})
}
}
}
</script>

View file

@ -1,9 +1,27 @@
import "virtual:windi.css"
import routes from "~pages"
import { createApp } from "vue"
import { createRouter, createWebHistory } from "vue-router"
import originalRoutes from "~pages"
import { createApp, FunctionalComponent } from "vue"
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router"
import App from "./App.vue"
import { createHead } from "@vueuse/head"
import { pageComponentLoading } from "./store"
const routes = originalRoutes.map(route => {
if (typeof route.component !== "function") return route
const load = route.component as (() => Promise<FunctionalComponent>)
return {
...route,
component: async () => {
pageComponentLoading.value = true
const value = await load()
pageComponentLoading.value = false
return value
}
}
}) as RouteRecordRaw[]
const router = createRouter({
history: createWebHistory(),

View file

@ -1,5 +1,5 @@
<template>
<TopBarLayout title="Blog" back-target="/">
<TopBarLayout title="Blog" back-target="/blog">
<article>
<h1 class="font-bold text-3xl sm:text-3xl sm:text-center font-special">
{{ post.title }}

View file

@ -58,7 +58,7 @@
const posts = await getPosts()
return {
posts: [...posts, ...posts, ...posts]
posts
}
}
}

3
src/store.ts Normal file
View file

@ -0,0 +1,3 @@
import { ref } from "vue"
export const pageComponentLoading = ref(false)