Add loading overlay
This commit is contained in:
parent
8ec13b18f0
commit
481e30c1b3
6 changed files with 156 additions and 15 deletions
|
@ -19,9 +19,6 @@
|
||||||
"windicss": "^3.2.1"
|
"windicss": "^3.2.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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/plus-jakarta-sans": "^4.5.0",
|
||||||
"@fontsource/syne": "^4.5.0",
|
"@fontsource/syne": "^4.5.0",
|
||||||
"@iconify/json": "1.1.444",
|
"@iconify/json": "1.1.444",
|
||||||
|
|
129
src/App.vue
129
src/App.vue
|
@ -1,10 +1,27 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="h-100vh w-100vw text-light-100 overflow-x-hidden">
|
<div class="h-100vh w-100vw text-light-100 overflow-x-hidden">
|
||||||
|
<suspense @pending="startLoading()" @resolve="stopLoading()">
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
<suspense>
|
<suspense @pending="startLoading()" @resolve="stopLoading()">
|
||||||
<component :is="Component"/>
|
<component :is="Component"/>
|
||||||
</suspense>
|
</suspense>
|
||||||
</router-view>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -16,10 +33,116 @@
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
font-size: 17px;
|
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>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
|
import { computed, ref } from "vue"
|
||||||
|
import { useWindowSize, whenever } from "@vueuse/core"
|
||||||
|
import { pageComponentLoading } from "./store"
|
||||||
|
|
||||||
export default {
|
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>
|
</script>
|
||||||
|
|
24
src/main.ts
24
src/main.ts
|
@ -1,9 +1,27 @@
|
||||||
import "virtual:windi.css"
|
import "virtual:windi.css"
|
||||||
import routes from "~pages"
|
import originalRoutes from "~pages"
|
||||||
import { createApp } from "vue"
|
import { createApp, FunctionalComponent } from "vue"
|
||||||
import { createRouter, createWebHistory } from "vue-router"
|
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router"
|
||||||
import App from "./App.vue"
|
import App from "./App.vue"
|
||||||
import { createHead } from "@vueuse/head"
|
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({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<TopBarLayout title="Blog" back-target="/">
|
<TopBarLayout title="Blog" back-target="/blog">
|
||||||
<article>
|
<article>
|
||||||
<h1 class="font-bold text-3xl sm:text-3xl sm:text-center font-special">
|
<h1 class="font-bold text-3xl sm:text-3xl sm:text-center font-special">
|
||||||
{{ post.title }}
|
{{ post.title }}
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
const posts = await getPosts()
|
const posts = await getPosts()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
posts: [...posts, ...posts, ...posts]
|
posts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
3
src/store.ts
Normal file
3
src/store.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import { ref } from "vue"
|
||||||
|
|
||||||
|
export const pageComponentLoading = ref(false)
|
Loading…
Add table
Add a link
Reference in a new issue